NAME

CGI::Application::Demo - A vehicle to showcase CGI::Application

Synopsis

#!/usr/bin/perl

use strict;
use warnings;

use CGI::Application::Demo;

# -----------------------------------------------

delete @ENV{'BASH_ENV', 'CDPATH', 'ENV', 'IFS', 'PATH', 'SHELL'}; # For security.

CGI::Application::Demo -> new() -> run();

Description

CGI::Application::Demo is a vehicle for the delivery of a sample CGI::Application application, with these components:

A CGI instance script
A text configuration file
A CSS file
A data file to help bootstrap populating the database
A set of command line scripts, to bootstrap populating the database
A set of HTML::Templates
A set of Perl modules
CGI::Application::Demo
CGI::Application::Demo::Base
CGI::Application::Demo::Create
CGI::Application::Demo::Faculty
CGI::Application::Demo::LogDispatchDBI

This module, CGI::Application::Demo, demonstrates various features available to programs based on CGI::Application:

Run modes and their subs
Disk-based session handling
Storing the session id in a hidden CGI form field
Using the session to store user-changeable options
Using Class::DBI and Class::DBI::Loader to auto-generate code per database table
Using HTML::Template style templates
Changing the run mode with Javascript
Overriding the default query object

This replaces a CGI object with a ligher-weight CGI::Simple object.

Initialization via a configuration file

This uses FindBin::Real to locate the config file at run time.

Switching database servers via the config file
Logging to a database table
Multiple inheritance, to support MySQL, Oracle and Postgres neatly

See CGI::Application::Demo::LogDispatchDBI.

Note: Because I use Class::DBI::Loader, which wants a primary key in every table, and I use CGI::Session, I changed the definition of my 'sessions' table from this:

create table sessions
(
	id char(32) not null unique,
	a_session text not null
);

to this:

create table sessions
(
	id char(32) not null primary key,
	a_session text not null
);

compared to what's recommended in the CGI::Session docs.

Also, as you add complexity to this code, you may find it necessary to change line 13 of Base.pm from this:

use base 'Class::DBI';

to something like this:

use base $^O eq 'MSWin32' ? 'Class::DBI' : 'Class::DBI::Pg'; # 'Class::DBI::Oracle';

Distributions

This module is available both as a Unix-style distro (*.tgz) and an ActiveState-style distro (*.ppd). The latter is shipped in a *.zip file.

See http://savage.net.au/Perl-modules/html/installing-a-module.html for help on unpacking and installing each type of distro.

Order of Execution of subs within a CGI::Application-based script:

The instance script

The instance script (see Synopsis) contains 'use CGI::Application::Demo', which causes Perl to load the file /perl/site/lib/CGI/Application/Demo.pm.

At this point the instance script is initialized, in that package CGI::Application::Demo has been loaded. The script has not yet started to run.

This package contains "use base 'CGI::Application'", meaning CGI::Application::Demo is a descendent of CGI::Application. That is, CGI::Application::Demo is-a CGI::Application.

This (CGI::Application::Demo) is what I'll call our application module.

What's confusing is that application modules can declare various hooks (a hook is an alias for a sub) to be run before the sub corresponding to the current run mode. Two of these hooked subs are called cgiapp_init() (hook is 'init'), and cgiapp_prerun() (hook is 'prerun').

Further, a sub prerun_mode() is also available.

None of these 3 sub are called yet, if at all.

The instance script, revisited

Now CGI::Application::Demo -> new() is called, and it does what it has to do.

This is, it initializes a new object of type CGI::Application.

This includes calling the 'init' hook (sub cgiapp_init() ) and sub setup(), if any.

Since we did in fact declare a sub cgiapp_init() (hook is 'init'), that gets called, and since we also declared a sub setup(), that then gets called too.

You can see the call to setup() at the very end of CGI::Application's sub new().

Oh, BTW, during the call to cgiapp_init, there was a call to sub setup_db_interface(), which, via the magic of Class::DBI::Loader, tucks away an array ref of a list of classes, one per database table, in the statement $self -> param(cgi_app_demo_classes => $classes), and an array ref of a list of table names in the statement $self -> param(cgi_app_demo_tables => $tables).

The instance script, revisited, again

Now CGI::Application::Demo -> run() is called.

First, this calls our sub cgiapp_get_query() via a call to sub query(), which we declared in order to use a light-weight object of type CGI::Simple, rather than an object of type CGI.

Then, eventually, our application module's run mode sub is called, which defaults to sub start().

So, sub start() is called, and it does whatever we told it to do. The app is up and running, finally.

Required Modules

Carp
CGI::Application
CGI::Application::Plugin::Config::Context
CGI::Application::Plugin::LogDispatch
CGI::Application::Plugin::Session
CGI::Simple
Class::DBI
Class::DBI::Loader
Config::General
FindBin::Real
HTML::Template
Log::Dispatch::DBI

Prerequisites of the Required Modules

Of course, the above modules depend on others. Here's a list I kept when I recently installed them all on a PC not connected to the internet.

The list also includes a very small number of modules not directly relevant to CGI::Application::Demo, but does conveniently include those modules required by DBIx::Admin. This saves me having to copy this list into the docs for DBIx::Admin.

And yes, this list does include some shipped with Perl.

Firstly though, I install GnuPG, since Module::Signature would like to play with it.

This is not a Perl module, but is a package from http://www.gnupg.org/.

Then, I install these Perl modules manually in this order (i.e. before using my unreleased Local::Build to install the rest). 'Manual' here really means I need these to install Local::Build.

In each case, I use the 'Perl Makefile.PL' method of installing, except for Module::Build, which insists on 'Perl Build.PL'.

Also, the latter module complains, so I install it twice.

CGI
HTML::Template
IPC::Run3
ExtUtils::MakeMaker
ExtUtils::CBuilder
ExtUtils::ParseXS
Digest
Digest::SHA
PAR::Dist
Module::Signature

This module asks you one question during installation (sigh).

Module::Build
Module::Which
DBI
PathTools
Algorithm::Diff
Archive::Tar
Compress::Zlib
IO::Zlib
Text::Diff
YAML

Having installed those, I now install some of my own modules, in this order:

Local::Run3
Local::Build

Now, all of the following modules can be installed using Local::Build, in this order:

Devel::Symdump
Test::Harness
Test::Simple
Pod::Escapes
Pod::Simple
Pod::Parser
Pod::Coverage
Test::Pod
Test::Pod::Coverage
Sub::Uplevel
Test::Exception
UNIVERSAL::moniker
UNIVERSAL::require
Hook::LexWrap
Sub::WrapPackages
Clone
version
Storable
FindBin::Real
File::Temp
HTML::Entities::Interpolate
HTML::FillInForm
HTML::Parser
Scalar::List::Utils
CGI::Simple
CGI::Session
SQL::Statement
Text::CSV_XS
DBD::CSV
Return::Value
Email::Address
Email::Simple
Email::Send
Attribute::Handlers
Params::Validate
DBI
DBD::mysql
DBD::Pg
DBIx::HTML::PopupRadio
Class::Accessor
Class::Accessor::Chained
Data::Page
IO::stringy
Class::Data::Inheritable
Class::ISA
Class::Trigger
DBIx::ContextualFetch
Ima::DBI
Class::DBI
Class::DBI::mysql
Class::DBI::Oracle
Class::DBI::Pg
Class::DBI::Loader
Log::Dispatch
Log::Dispatch::DBI
Hash::Merge
Config::General
Config::Context
CGI::Application
CGI::Application::Plugin::Config::Context
CGI::Application::Plugin::LogDispatch
CGI::Application::Plugin::Session
Tie::Function
Time::HiRes
Time::Piece
Lingua::EN::Inflect
Lingua::EN::Numbers

The end result of this is a list of modules needed by any CGI::Application-type app which uses a few plugins.

Installing the non-Perl components of this module

Unpack the distro, and you'll see various directories to be moved to where your web server can find them. I'll assume you're running Apache, and hence I suggest these locations:

cgi-bin/cgi-app-demo/

Copy this cgi-app-demo/ to Apache's cgi-bin/.

conf/cgi-app-demo/

Copy this cgi-app-demo/ to Apache's conf/.

css/cgi-app-demo/

Copy css/ to Apache's document root.

templates/cgi-app-demo/

Copy templates/ to Apache's document root.

Now you may have to edit a line or two in some files.

I realise all this seems to be a bit of an effort, but once you appreciate the value of such configuation options, you'll adopt them as enthusiastically as I have done. And you only do this once.

Here I just list the lines you should at least consider editing:

cgi-app-demo.conf
<Location /cgi-bin/cgi-app-demo/cgi-app-demo.cgi>

css_url=/css/cgi-app-demo/cgi-app-demo.css

dsn=dbi:mysql:cgi_app_demo, username and password

tmpl_path=/apache2/htdocs/templates/cgi-app-demo/
Demo.pm
my($config_file) = FindBin::Real::Bin() . '/../../conf/cgi-app-demo/cgi-app-demo.conf';
Base.pm
my($config_file) = FindBin::Real::Bin() . '/../../conf/cgi-app-demo/cgi-app-demo.conf';

Also, if you edited the Location line in cgi-app-demo.conf, make a matching change here:

$config = $config{'Location'}{'/cgi-bin/cgi-app-demo/cgi-app-demo.cgi'};
Create.pm

Again, if you edited the Location line in cgi-app-demo.conf, make a matching change here:

my($config) = $config{'Location'}{'/cgi-bin/cgi-app-demo/cgi-app-demo.cgi'};

Note also this line, although you won't need to edit it if you stick to these instructions:

$input_file_name = FindBin::Real::Bin() . "/../data/$input_file_name";
cgi-bin/cgi-app-demo/cgi-app-demo.cgi

Patch the 'use lib' line if you've installed your modules in a non-standard location.

$distro/scripts/test-conf.pl

Patch these two lines, if necessary:

my($config_file) = "$ENV{'CONFIG'}/cgi-app-demo.conf";
my($config)      = $config{'Location'}{'/cgi-bin/cgi-app-demo/cgi-app-demo.cgi'};
$distro/scripts/drop.pl, create.pl and populate.pl

In these, you need to set the environment variables (which are not used by *.cgi):

CONFIG=/apache2/conf
INSTALL=/perl/site/lib

Then you might need to edit this line (if you edited the Location line in cgi-app-demo.conf):

config_file_name => "$ENV{'CONFIG'}/cgi-app-demo/cgi-app-demo.conf"

Initializing the Database

OK. Now edit distro/scripts/build or distro/scripts/build.bat to suit.

Lastly, cd $distro/scripts/ and run ./build or build.bat from the command line. This creates and populates the database.

Finally, point your web client at http://127.0.0.1/cgi-bin/cgi-app-demo/cgi-app-demo.cgi and see what happens.

A Note about HTML::Entities

In general, a CGI::Application-type app could be outputting any type of data whatsoever, and will need to protect that data by encoding it appropriately. For instance, we want to stop arbitrary data being interpreted as HTML.

The sub HTML::Entities::encode_entities() is designed for precisely this purpose. See that module's docs for details.

Now, in order to call that sub from within a double-quoted string, we need some sort of interpolation facility. Hence the module HTML::Entities::Interpolate. See its docs for details.

This demo does not yet need or use HTML::Entities::Interpolate.

Test Environments

I've tested this module in these environments:

GNU/Linux, Perl 5.8.0, Postgres 7.4.7, Apache 2.0.46
Win2K, Perl 5.8.6, MySQL 4.1.9, Apache 2.0.52

Credits

I drew significant inspiration from code in the CGI::Application::Plugin::BREAD project:

http://charlotte.pm.org/kwiki/index.cgi?BreadProject

I used those ideas to write my own bakermaker, the soon-to-be-released (Nov '05) DBIx::Admin.

In fact, the current module is a cut-down version of DBIx::Admin.

Author

CGI::Application::Demo was written by Ron Savage <ron@savage.net.au> in 2005.

Home page: http://savage.net.au/index.html

Copyright

Australian copyright (c) 2005, Ron Savage. All rights reserved.

All Programs of mine are 'OSI Certified Open Source Software';
you can redistribute them and/or modify them under the terms of
The Artistic License, a copy of which is available at:
http://www.opensource.org/licenses/index.html