I would like to create a mix between what dragonchild calls "clannish" and "socialist" (see Re: Re: Why CGI::Application?). One directory per functional area, and within that, one script per run mode.
*sighs* I was afraid that post was going to come back and haunt me. It was mostly tongue-in-cheek, attempting to get from "The Monolithic Script" to a C::A solution in a few easy steps. Don't read too much into it.
As for answering your question(s) ... hmmm ... In no particular order:
- I don't want to use yet another external module ...
Get over it. CGI::Application is pure Perl, which means it will run on any system Perl runs on. Many modules you might end up needing to install, including every database module, can't say this.
- I don't understand how it works
Don't bother. I didn't understand how it worked until I wanted to add something to it. Then, I did some sourcediving. All you need to know is that if you follow the published API, it will do what it promises. Period. Unless you need something more, that should be good enough. For now.
- Seems like most of what I am already doing is what CGI-App does. And I don't like the fact that all my code gets lumped into one single file
All your code will not be lumped into one single file. In fact, if you lump your code into one single file, you will be doing it wrong. I'll give an example a little further down.
- using as few external modules as possible
Then, you go on to list several external modules, some of which are the hardest to install on different systems. I have built Perl applications on some 7 different operating systems, including Solaris, Linux, and Windows. The hardest module to install? DBD::Oracle. The second hardest? DBD::mysql. The easiest? CGI::Application. I'm serious. I have NEVER had a problem installing C::A from the CPAN shell. Not once, in over 100 installs on various machines.
Now, for an example. This example assumes you understand that basic concepts of OO theory. (You don't have to understand Perl OO programming - just OO theory.)
The idea is that we have three functional areas to our application.
- The public area, which contains stuff everyone wants. This is unsecured.
- The member's only area, which contains stuff you have to pay for. You have to login for this.
- The admin area. Again, you have to login for this.
A few further, somewhat arbitrary, requirements:
- You should only ever have to login once for both member's and admin, if you're allowed in both.
- The code should be easy to test
- The pages should be viewable in both HTML and PDF.
Stop and think about how you'd do this in a bunch of CGI scripts. You'd have to at least have
- a print() function, to handle the dispatch between HTML and PDF
- a set of login functions, to handle the logging in/out of users
- a set of cookie functions, to handle setting/getting the cookie
- a set of session functions, to deal with the session identified by the cookie
- a dispatching function, to dispatch to the right CGI script
Now, this is no different that with C::A - you will have to have all those same functions. And, frankly, the code will be almost the same, line for line. But, there's a difference - with the CGI script method you're proposing, you will need to make sure you call all those functions in the right order in every single file. If you want to change that order, you will need to change every single CGI script. Every single one. That's a lot of work!
With C::A, you put that kind of code in one place, and only one place. Then, C::A guarantees that the code you specify will be called before the runmode. This way, by the time you get into the runmode, you know a whole bunch of stuff has already happened. For example, you know that
- Security has already been checked
- The cookie has already been retrieved
- The session has already been retrieved
- Any database handles have already been instantiated
And, when you are done, you will also be guaranteed than any cleanup work will be done for you.
Every. Single. Time.
So, how do you get C::A to do all this magic for you? By using the power of subclassing. When I use C::A, I create a child-class, generally called Generic::Application. This is my personal C::A, modified and customized to meet my personal needs. I'll generally have 2-3 things in it:
- That print() function, in all its glory. I prefer Template Toolkit, but HTML::Template is also good. I use PDF::Template and Excel::Template for other formats, but YMMV.
- Basic configuration file handling. I like Config::ApacheFormat, but it doesn't work for everyone.
- Basic cookie handling. Generally, I'm looking for bake_cookie() and read_cookie(), or something like that.
This class will not have anything in cgiapp_prerun() or cgiapp_postrun(). This is just to provide some common functionality.
Next, I'll create Specific::Application which will be a child of Generic::Application. This will be the base class for the specific web application I'm working on. In here will be the cgiapp_prerun() and cgiapp_postrun() that will generally do things like
- set up and teardown the config files
- set up and teardown database handles
- set up and teardown the template object (including template paths)
- set up and teardown the cookies / session / authentication
Each of those things may or may not be in separate methods.
Note, we haven't actually written any runmodes yet. This is all just infrastructure work - code we'll need later on down the road.
Now, remember back to our requirements - we need three areas. That sounds like we need three child classes. Specific::Application::Public, Specific::Application::Members, and Specific::Application::Admin. Each of these will also have a very basic .cgi file in the cgi-bin directory, corresponding to the last part of the classname.
Now, Specific::Application assumes that every page requires a login. We can then override that in Specific::Application::Public to say that runmodes in this class do not require a login. That's the more secure way of doing things. The admin stuff can be handled by the ::Admin class requiring that the session generated by authentication contain a certain flag set to true, indicating this is an admin user. All of this happens in the cgiapp_prerun() methods in the various classes. The same goes for cookies, databases, and the like.
Every runmode will look something like:
my $self = shift;
my $session = $self->param( 'session' );
my $dbh = $self->param( 'dbh' );
# Do stuff here
Everything else is handled for you. In fact, most of your developers will never need to know how things work - just that they do. Does that help?
Being right, does not endow the right to be rude; politeness costs nothing.
Being unknowing, is not the same as being stupid.
Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.
Are you posting in the right place? Check out Where do I post X? to know for sure.
Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
<code> <a> <b> <big>
<blockquote> <br /> <dd>
<dl> <dt> <em> <font>
<h1> <h2> <h3> <h4>
<h5> <h6> <hr /> <i>
<li> <nbsp> <ol> <p>
<small> <strike> <strong>
<sub> <sup> <table>
<td> <th> <tr> <tt>
Snippets of code should be wrapped in
<code> tags not
<pre> tags. In fact, <pre>
tags should generally be avoided. If they must
be used, extreme care should be
taken to ensure that their contents do not
have long lines (<70 chars), in order to prevent
horizontal scrolling (and possible janitor
Want more info? How to link
or How to display code and escape characters
are good places to start.