NAME
CGI::Application::Framework - Fully-featured MVC web application platform
VERSION
Version 0.26
NOTE
This is alpha-quality software which is being continually developed and refactored. Various APIs will change in future releases. There are bugs. There are missing features. Feedback and assistance welcome!
SYNOPSIS
Application Layout
A CGI::Application::Framework
project has the following layout:
/cgi-bin/
app.cgi # The CGI script
/framework/
framework.conf # Global CAF config file
projects/
MyProj/
framework.conf # MyProj config file
common-templates/ # login templates go here
common-modules/
CDBI/
MyProj.pm # The Class::DBI project module
MyProj/
app.pm # The Class::DBI application module
MyProj.pm # The project module
applications/
framework.conf # "All applications" config file
myapp1/
framework.conf # myapp1 config file
myapp1.pm # An application module
templates/
runmode_one.html # templates for myapp1
runmode_two.html
myapp2/
framework.conf # myapp2 config file
myapp2.pm # An application module
templates/
runmode_one.html # templates for myapp2
runmode_two.html
The CGI Script
You call your application with an URL like:
http://www.example.com/cgi-bin/app.cgi/MyProj/myapp
By default, CAF applications are divided first into Projects
and then into applications
. Based on the URL above, the Project is called MyProj
and the application within that project is called myapp
.
The actual CGI script (app.cgi
) is tiny. It looks like this:
#!/usr/bin/perl
use strict;
use CGI::Application::Framework;
use CGI::Carp 'fatalsToBrowser';
CGI::Application::Framework->run_app(
projects => '../caf/projects',
);
An application module
Your application module (myapp.pm
) looks like a standard CGI::Application
, with some extra features enabled.
package myapp1;
use strict;
use warnings;
use base qw ( MyProj );
sub setup {
my $self = shift;
$self->run_modes([qw( runmode_one )]);
$self->start_mode('runmode_one');
}
sub rumode_one {
my $self = shift;
$self->template->fill({
name => $self->session->{user}->fullname,
});
}
A template
The template is named (by default) after the run mode that called it. In this case, it's runmode_one.html
:
<html>
<body>
<h1>Welcome</h1>
Hello there, <!-- TMPL_VAR NAME="name" -->
</body>
</html>
The project module
Your common project module (MyProj.pm) contains a lot of code, but most of it can be copied directly from the examples:
package MyProj;
use warnings;
use strict;
use base 'CGI::Application::Framework';
# Called to determine if the user filled in the login form correctly
sub _login_authenticate {
my $self = shift;
my $username = $self->query->param('username');
my $password = $self->query->param('password');
my ($user) = CDBI::Project::app::Users->search(
username => $username
);
if ($password eq $user->password) {
return(1, $user);
}
return;
}
# Called to determine if the user filled in the re-login form correctly
sub _relogin_authenticate {
my $self = shift;
my $password = $self->query->param('password');
my $user = CDBI::Project::app::Users->retrieve(
$self->session->{uid}
);
if ($password eq $user->password) {
return(1, $user);
}
return;
}
# _login_profile and _relogin_profile are
# definitions for Data::FormValidate, as needed by
# CGI::Application::Plugin::ValidateRM
sub _login_profile {
return {
required => [ qw ( username password ) ],
msgs => {
any_errors => 'some_errors', # just want to set a true value here
prefix => 'err_',
},
};
}
sub _relogin_profile {
return {
required => [ qw ( password ) ],
msgs => {
any_errors => 'some_errors', # just want to set a true value here
prefix => 'err_',
},
};
}
# Return extra values for the login template for when
sub _login_failed_errors {
my $self = shift;
my $is_login_authenticated = shift;
my $user = shift;
my $errs = undef;
if ( $user && (!$is_login_authenticated) ) {
$errs->{'err_password'} = 'Incorrect password for this user';
} elsif ( ! $user ) {
$errs->{'err_username'} = 'Unknown user';
} else {
die "Can't happen! ";
}
$errs->{some_errors} = '1';
return $errs;
}
# Return error values for the relogin template
sub _relogin_failed_errors {
my $self = shift;
my $is_login_authenticated = shift;
my $user = shift;
my $errs = undef;
if ( $user && (!$is_login_authenticated) ) {
$errs->{err_password} = 'Incorrect password for this user';
} elsif ( ! $user ) {
$errs->{err_username} = 'Unknown username';
$self->log_confess("Can't happen! ");
}
$errs->{some_errors} = '1';
return $errs;
}
# Here we handle the logic for sessions timing out or otherwise becoming invalid
sub _relogin_test {
my $self = shift;
if ($self->session->{_timestamp} < time - 600) {
return 1;
}
return;
}
# Whenever a session is created, we have the opportunity to
# fill it with any values we like
sub _initialize_session {
my $self = shift;
my $user = shift;
$self->session->{user} = $user;
}
# Provide values to the relogin template
sub _relogin_tmpl_params {
my $self = shift;
return {
username => $self->session->{'user'}->username;
};
}
# Provide values to the login template
sub _login_tmpl_params {
my $self = shift;
}
The database classes
By convention, the database classes are also split into Project and application. First the project level CDBI::MyProj
:
package CDBI::MyProj;
use base qw( CGI::Application::Framework::CDBI );
use strict;
use warnings;
1;
Next, the application-specific CDBI::Project::app
package CDBI::Project::app;
use Class::DBI::Loader;
use base qw ( CDBI::MyProj );
use strict;
use warnings;
sub db_config_section {
'db_myproj';
}
sub import {
my $caller = caller;
$caller->new_hook('database_init');
$caller->add_callback('database_init', \&setup_tables);
}
my $Already_Setup_Tables;
sub setup_tables {
return if $Already_Setup_Tables;
my $config = CGI::Application::Plugin::Config::Context->get_current_context;
my $db_config = $config->{__PACKAGE__->db_config_section};
my $loader = Class::DBI::Loader->new(
dsn => $db_config->{'dsn'},
user => $db_config->{'username'},
password => $db_config->{'password'},
namespace => __PACKAGE__,
relationships => 0,
);
$Already_Setup_Tables = 1;
}
1;
Configuration
By default, there are four levels of configuration files in a CAF application: global, project, all apps and application:
/caf/
framework.conf # Global CAF config file
projects/
MyProj/
framework.conf # MyProj config file
applications/
framework.conf # "All applications" config file
myapp1/
framework.conf # myapp1 config file
myapp2/
framework.conf # myapp2 config file
When an application starts, the application-level framework.conf is loaded. This config file typically contains the line:
<<include ../framework.conf>>
Which includes the "all apps" configuration file. Similarly this configuration file includes the project-level configuration file, and so on up the chain until we reach the top-level framework.conf
.
CAF uses the Config::Context
configuration system, which is compatible with multiple configuration file formats. The default configuration format is Config::General
, which means apache-style config files:
md5_salt = bsdjfgNx/INgjlnVlE%K6N1BvUq9%#rjkfldBh
session_timeout = 300
<SessionParams>
object_store = Apache::Session::DB_File
LockDirectory = ../caf/sessions/locks
FileName = ../caf/sessions/database
</SessionParams>
<TemplateOptions>
include_path_common common-templates
# template types include: HTMLTemplate, TemplateToolkit and Petal
default_type HTMLTemplate
</TemplateOptions>
<SystemTemplateOptions>
include_path_common common-templates
default_type HTMLTemplate
<HTMLTemplate>
cache 1
global_vars 1
die_on_bad_params 0
</HTMLTemplate>
</SystemTemplateOptions>
<LogDispatch>
<LogName file>
module = Log::Dispatch::File
filename = ../caf/logs/webapp.log
min_level = debug
mode = append
</LogName>
append_newline = 1
format = [%P][%d] %F %L %p - %m%n
</LogDispatch>
<db_myproj>
dsn = DBI:mysql:dbname=project
username = dbuser
password = seekrit
</db_myproj>
DESCRIPTION
CGI::Application::Framework
is a web development plaform built upon CGI::Application
. It incorporates many modules from CPAN in order to provide a feature-rich environment, and makes it easy to write robust, secure, scalable web applications.
It has the following features:
Model-View-Controller (MVC) development with CGI::Application
Choice of templating system (via CGI::Application::Plugin::AnyTemplate)
Form Validatation and Sticky Forms (via CGI::Application::Plugin::ValidateRM)
Easy (optional) Class::DBI integration
Session Management (Apache::SessionX)
Authentication
Login Managment
login form
relogin after session timeout
form state is saved after relogin
Powerful configuration system (via CGI::Application::Plugin::Config::Context)
Link Integrity system
Logging (via CGI::Application::Plugin::Log::Dispatch)
STARTUP (app.cgi and the run_app method)
You call your application with an URL like:
http://www.example.com/cgi-bin/app.cgi/MyProj/myapp?rm=some_runmode
This instructs CAF to run the application called myapp
which can be found in the project called MyProj
. When CAF finds this module, it sets the value of the rm
param to some_runmode
and runs the application.
All of your applications are run through the single CGI script. For instance, here are some examples:
http://www.example.com/cgi-bin/app.cgi/Admin/users?rm=add
http://www.example.com/cgi-bin/app.cgi/Admin/users?rm=edit
http://www.example.com/cgi-bin/app.cgi/Admin/documents?rm=publish
http://www.example.com/cgi-bin/app.cgi/Library/search?rm=results
The actual CGI script (app.cgi
) is tiny. It looks like this:
#!/usr/bin/perl
use strict;
use CGI::Application::Framework;
use CGI::Carp 'fatalsToBrowser';
CGI::Application::Framework->run_app(
projects => '../caf/projects',
);
All the magic happens in the run_app
method. This method does a lot of magic in one go:
* examines the value of the URL's C<PATH_INFO>
* determines the correct application
* finds the application's config file
* finds the application's module file
* adds paths to @INC, as appropriate
* adds paths to the application's TMPL_PATH
* passes on any PARAMS or QUERY to the application's new() method
* runs the application
The only required option is the location of the CAF projects
directory. The full list of options are:
- projects
-
Location of the CAF top-level projects directory. Required.
- app_params
-
Any extra parameters to pass as the
PARAMS
option to the application'snew
method. undefined by default. - query
-
A CGI query object to pass as the
QUERY
option to the application'snew
method. undefined by default - common_lib_dir
-
Where the Perl modules for this project are stored. Defaults to:
$projects/$project_name/common-modules
The value of this parameter will be added to the application's @INC.
- common_template_dir
-
Where the templates common to all apps in this project are stored. Defaults to:
$projects/$project_name/common-templates
- app_dir
-
Where the application Perl modules are stored. Defaults to:
$projects/$project_name/applications/$app_name/
The value of this parameter will be added to the application's @INC.
- app_template_dir
-
Where the application-specific template files are. Defaults to:
$app_dir/templates
- module
-
The filename of the application module. Defaults to:
$app_name.pm
The run_app
method in CAF was inspired by Michael Peter's CGI::Application::Dispatch module, and implements a similar concept.
TEMPLATES
CAF uses the CGI::Application::Plugin::AnyTemplate system. AnyTemplate
allows you to use any supported Perl templating system, and switch between them while using the same API.
Currently supported templating systems include HTML::Template, HTML::Template::Expr, Template::Toolkit and Petal.
Syntax
The syntax is pretty flexible. Pick a style that's most comfortable for you.
- CGI::Application::Plugin::TT style syntax
-
$self->template->process('edit_user', \%params);
or (with slightly less typing):
$self->template->fill('edit_user', \%params);
- CGI::Application load_tmpl style syntax
-
my $template = $self->template->load('edit_user'); $template->param('foo' => 'bar'); $template->output;
Defaults
If you don't specify a filename, the system loads a template named after the current run mode.
If you do specify a filename, you typically omit the filanme's extension. The correct extension is added according to the template's type.
sub add_user {
my $self = shift;
$self->template->fill; # shows add_user.html
# or add_user.tmpl
# or add_user.xhtml
# (depending on template type)
}
sub del_user {
my $self = shift;
$self->template('are_you_sure')->fill;
# shows are_you_sure.html
# or are_you_sure.tmpl
# or are_you_sure.xhtml
# (depending on template type)
}
The default template type is specified in the CAF configuration file. along with the other template options.
Template Configuration
Here are the template options from the default top-level framework.conf
:
<TemplateOptions>
include_path_common common-templates
# template types include: HTMLTemplate, TemplateToolkit and Petal
default_type HTMLTemplate
# Default options for each template type
<HTMLTemplate>
cache 1
global_vars 1
die_on_bad_params 0
</HTMLTemplate>
<TemplateToolkit>
POST_CHOMP 1
</TemplateToolkit>
<Petal>
POST_CHOMP 1
</Petal>
</TemplateOptions>
System Templates
In addition to regular templates there are also system templates. These are used to display the templates for the various runmodes that are called automatically:
* invalid_checksum.html
* invalid_session.html
* login.html
* login_form.html
* relogin.html
* relogin_form.html
You can use a different set of options for the system templates than you use for your ordinary templates. For instance you can use Template::Toolkit for your run mode templates, but use HTML::Template for the login and relogin forms.
The options for the system templates are defined in the SystemTemplateOptions
section in the top level framework.conf
:
<SystemTemplateOptions>
include_path_common common-templates
default_type HTMLTemplate
<HTMLTemplate>
cache 1
global_vars 1
die_on_bad_params 0
</HTMLTemplate>
</SystemTemplateOptions>
With both TemplateOptions
and SystemTemplateOptions
the configuration structure maps very closely to the data structure expected by CGI::Application::Plugin::AnyTemplate. See the docs for that module for further configuration details.
Where Templates are Stored
Application Templates
By default, your application templates are stored in the templates
subdirectory of your application directory:
/framework/
projects/
MyProj/
applications/
myapp1/
templates/
runmode_one.html # templates for myapp1
runmode_two.html
myapp2/
templates/
runmode_one.html # templates for myapp2
runmode_two.html
Project Templates
By default, project templates are stored in the common-templates
subdirectory of your project directory:
projects/
MyProj/
common-templates/ # login and other common
# templates go here
Pre- and Post- process
You can hook into the template generation process so that you can modify every template created. Details for how to do this can be found in the docs for to CGI::Application::Plugin::AnyTemplate.
EMBEDDED COMPONENTS
Embedded Components allow you to include application components within your templates.
For instance, you might include a header component a the top of every page and a footer component at the bottom of every page.
These componenets are actually first-class run modes. When the template engine finds a special tag marking an embedded component, it passes control to the run mode of that name. That run mode can then do whatever a normal run mode could do. But typically it will load its own template and return the template's output.
This output returned from the embedded run mode is inserted into the containing template.
The syntax for embed components is specific to each type of template driver.
Syntax
HTML::Template syntax:
<TMPL_VAR NAME="CGIAPP_embed('some_run_mode')">
HTML::Template::Expr syntax:
<TMPL_VAR EXPR="CGIAPP_embed('some_run_mode')">
Template::Toolkit syntax:
[% CGIAPP.embed("some_run_mode") %]
Petal syntax:
<span tal:replace="structure CGIAPP/embed 'some_run_mode'">
this text gets replaced by the output of some_run_mode
</span>
In general, the code for some_run_mode
looks just like any run mode. For detailed information on how to use the embedded component system, including how to pass parameters to run modes, see the docs for CGI::Application::Plugin::AnyTemplate
.
SESSIONS
A session is a scratchpad area that persists even after your application exits. Each user has their own individual session.
So if you store a value in the session when Gordon is running the application, that value will be private for Gordon, and independent of the value stored for Edna.
Sessions are accessible via $self->session
:
$self->session->{'favourite_colour'} = 'blue';
# time passes... and eventually the application is run a second time by
# the same user...
my $colour = $self->session->{'favourite_colour'};
$self->template->fill('colour' => $colour);
LINKS
When using CGI::Application::Framework
, it is not recommended that you create your own hyperlinks from page to page or that you modify the links that CAF creates. When you create an URL with one of the URL-generation methods, CAF adds a checksum value to the URL. When the URL is followed, CAF verifies its integrity by validating the checksum.
If the user tampers with the checksum, they are redirected to a page with a severe warning, and their session is destroyed.
So it's best to create URL's using the utility methods provided.
Having said that, these routines are still not very friendly, and there is still work to be done in this area.
TODO:
* easily make links to another app in the same project
* easily make links to an app in a different project
- make_self_url
-
my $url = $self->make_self_url;
- make_link
-
my $url = $self->make_link(url => $self->query->url); my $url = $self->make_link(url => $other_url);
Options for make_link
- url
-
The base URL (without query string). Defaults to the URL for the current application.
- params
- with_checksum
- redirect
-
This is just a utility method to perform an HTTP redirect:
$self->redirect($self->make_link(url => $other_url));
LOGGING
You can send logging messages via the log
method:
$self->log->info('Information message');
$self->log->debug('Debug message');
The various log levels available are:
debug
info
notice
warning
error
critical
alert
emergency
You can set up handlers for any of these levels in the framework.conf
file. By default, the single handler installed only logs messages that are of the warning
level or higher (i.e. it only logs messages of the following levels: warning
, error
, critical
, alert
, emergency
).
<LogDispatch>
<LogName file>
module = Log::Dispatch::File
filename = ../framework/projects/logs/webapp.log
min_level = warning
mode = append
</LogName>
append_newline = 1
format = [%P][%d] %F %L %p - %m%n
</LogDispatch>
If you change the min_level
line to:
min_level = info
Then the handler will also log all info
and notice
messages as well. If you change it to:
min_level = debug
Then the handler will log all messages.
The following methods in $self
are useful for logging a message and exiting the application all in one step:
- log_croak
-
Logs your message, indicating the caller, and then dies with the same message:
$self->log_croak("Something bad happened");
- log_confess
-
Logs your message with a full stacktrace, and then dies with the same message and stacktrace:
$self->log_confess("Something bad happened - here's a ton of info");
The following methods in $self
are useful for logging a message and also printing a standard warning message to STDERR in the same step:
- log_carp
-
Logs your message, indicating the caller and then gives off a warning with the same message:
$self->log_carp("Something strange is happening");
- log_cluck
-
Logs your message with a full stack trace, and then gives off a warning with the same message and stacktrace:
$self->log_cluck("Something strange is happening - here's a ton of info");
AUTHENTICATION
Currently, all users of a CAF application have to login in order to use the system. This will change in a future release.
Application Flow
If the user is not logged in yet, they are taken to the login page. If they log in successfully, they are taken to the run mode they were originally destined for. Otherwise they are returned to the login page and presented with an error message.
After the user has logged in, you may force them to log in again if certain conditions are met. For instance, you might to decide to force users who have been idle for a certain period of time to log in again.
Runmodes
If you want, you can override the following runmodes. A good place to do this is in your project module.
- login
-
This runmode presents the login screen.
- relogin
-
This runmode presents the login screen with the user's name already filled in.
- invalid_session
-
This runmode displays "invalid session" template.
- invalid_checksum
-
This runmode displays "invalid checksum" template.
Authentication Hooks
You MUST provide the following authentication methods to define the behaviour of your application. A good place to do this is in your project module. You can copy the methods in the Example project module to get some sensible defaults.
- _login_authenticate
-
This method is expected to look at the
$query
object and determine if the user has successfully logged in. The method should return a two-element list indicating whether the user exists and whether or not the password was correct:(0, undef) --> Unknown user (0, $user) --> user was found, incorrect password given (1, $user) --> user was found, password given correct
- _relogin_authenticate
-
This method is similar to _login_authenticate. It is expected to determine the user's id from the session, and the password from the query object.
The method should return a two-element list indicating whether the user exists and whether or not the password was correct:
(0, undef) --> Unknown user (0, $user) --> user was found, incorrect password given (1, $user) --> user was found, password given correct
- _login_profile
-
This is a Data::FormValidate definition, needed by CGI::Application::Plugin::ValidateRM
The specifics of this should match the needs of your
login.html
form-displaying HTML::Template. - _relogin_profile
-
This is a Data::FormValidate definition, needed by CGI::Application::Plugin::ValidateRM
The specifics of this should match the needs of your
relogin.html
form-displaying HTML::Template. - _login_failed_errors
-
It has already been determined that the user did not successfully log into the application. So, create some error messages for the HTML template regarding the 'login' mode to display. This subroutine returns $err which is a hashref to key/value pairs where the key is the name of the template variable that should be populated in the event of a certain kind of error, and the value is the error message it should display.
Framework.pm provides $is_login_authenticated and $user parameters to this subroutine so that this sub can perform the necessary login checks.
Note that this isn't the same as that the login form was not well-constructed. Determining what is and what is not a syntactically valid login form, and the generation of any needed error messages thereof, is handled by the aspect of Framework.pm that calls uses _login_profile, so make sure that whatever you need to do along these lines is reflected there.
- _relogin_failed_errors
-
Similar to _login_failed_errors but for the
relogin.html
- _relogin_test
-
Here you do whatever you have to do to check to see if a transfer from run mode -to- run mode within an application is good. The return value should be:
1 - the relogin test has been successfully passed (implying no relogin authentication check) 0 - the relogin test has been failed (implying a relogin authentication check is forced)
For example, a good candidate is to check for a "timeout". If the user hasn't loaded a page within the application in some duration of time then return 1 -- meaning that a reauthentication isn't necessary. If a reauthentication is necessary then return 0.
- _initialize_session
-
This method can be used to set whatever session variables make sense in your application (or really in your collection of applications that use this base class) given that a first-time successful login has just occured.
- _relogin_tmpl_params
-
This is used to provide template variables to the "relogin" form In this case, the logical things to provide to the relogin form are uid and username; your application logic might differ. Likely you should keep all of this information the
$self->session
, and you probably should have populated the data into the session in the_initialize_session
method. - _login_tmpl_params
-
This is used to provide template variables to the "login" form.
CONFIGURATION
CGI::Application::Framework
uses CGI::Application::Plugin::Config::Context
for its configuration system. Config::Context
supports multiple configuration backends: Config::General
, Config::Scoped
, and XML::Simple
.
By default Config::General
format is used. This format is similar to Apache's configuration format.
It allows for single values:
colour = red
flavour = pineapple
And it allows for sections and subsections:
<produce>
<apple>
colour red
</apple>
<strawberry>
colour green
</strawberry>
</produce>
Additionally the Config::Context
allows for dynamic configuration based on the runtime context of the current application.
This is similar to Apache's configuration contexts. It looks like this:
<Location /shop>
title = ACME Coyote Supply Ltd.
</Location>
<LocationMatch admin>
title = ACME Widgets INC - Site Administration
</LocationMatch>
This allows you to use a single configuration file for multiple applications. It also allows you to make a single application accessible through multiple URLs or virtual hosts; and the the way the application is called determines its configuration.
Contexts are merged as well:
<Location /shop>
title = ACME Coyote Supply Ltd.
</Location>
<LocationMatch rockets>
subtitle = - Rocket Launchers
</LocationMatch>
<LocationMatch tnt>
subtitle = - Dynamite
</LocationMatch>
<LocationMatch magnets>
subtitle = - Giant Magnets
</LocationMatch>
By convention, in CAF projects there are four levels of configuration file: Site-wide (also calld top-level), project and appplication:
/framework/
framework.conf # Global CAF config file
projects/
MyProj/
framework.conf # MyProj config file
applications/
framework.conf # "All apps" config file
myapp1/
framework.conf # myapp1 config file
myapp2/
framework.conf # myapp2 config file
When a web request comes in to the system, these files are read in the order of bottom to top: application, then "all apps" then project, then site. Settings made in the lower level files override settings made in the higher level files. Each framework.conf contains the line:
<<include ../framework.conf>>
Which pulls in the configuration of its parent.
So the combination of context based matching plus per-application config files gives you a lot of flexibility.
Configuration isn't just limited to setting options in your application. Your application can pull its current configuration and put it into its template.
For instance (using a single project wide config, matching on application URL):
# in project framework.conf
<location /bookshop>
<extra_template_params>
title = ACME Roadrunner Reference, INC
background = parchement.gif
</extra_template_params>
</location>
<location /flowershop>
<extra_template_params>
title = ACME Exploding Flowers, INC
background = paisley.gif
</extra_template_params>
</location>
# in myapp.pm
sub run_mode {
my $self = shift;
my $config = $self->config->context;
my $extra_params = $config->{'extra_template_params'}
$self->template->fill($extra_params);
}
Alternately you could skip the location matching and just have a separate config file for each application. Or you can mix and match approaches.
Advanced Topic: Customizing the Configuration System
The following methods are provided as hooks for you to override the configuration system if you need to do that.
- config_context_options
-
This sub returns the arguments passed to
$self->conf->init
. By providing your own arguments, you can change the config backend fromConfig::General
to a different backend. - config_file
-
This is the full URL to the application config file. Since this configuration file includes its parent, it is the entry point into the configuration system. You can change this value, but if you do, none of the higher up configurations will be loaded.
By default, the full URL to the application configuration file is determined using information from the
run_app
method. - config_name
-
CGI::Application::Plugin::Config::Context
allows multiple named configuration profiles:$self->conf('fred')->init(...); $self->conf('barney')->init(...);
This allows you to have multiple simultaneous configurations loaded, each using its own options and backend.
By returning a value from
config_name
you tell CAF to use that name for accessing the configuration.For instance by doing the following:
sub config_name { 'system'; }
You would be effectively telling the configuration system to access the configuration like so:
my $config = $self->conf('system')->context;
This would separate out the CAF configuration from your own application configuration.
Note however that if you wanted the default configuration (i.e.
$self->config->context
to still work, you would need to set it up yourself by calling$self->conf->init
in yourcgiapp_init
method. - db_config_file
-
This is the name of the database config file. By default it is the same as
$self->config_file
, but you could change it if you wanted to keep the database configuration separate from your general application configuration. - db_config_name
-
This is the name of the database config name. By default it is undef, the same as
$self->config_name
. See config_name, above. - template_config_name
-
The
AnyTemplate
system also allows for multiple simultaneous configurations. By default thetemplate_config_name
isundef
so you can just say:$self->template->fill(...)
However you can set up multiple, named template configurations so that you can use:
$self->template('ht')->fill(...) $self->template('tt')->fill(...)
By setting
template_config_name
you are just telling the system what name to use when initializing the template system. - system_template_config_name
-
Similar to
template_config_name
this method allows you to set the name used for system templates (e.g. login, relogin, invalid checksum, etc.). By default it iscaf_system_templates
.
DATABASE SUPPORT
Note: The database support in CGI::Application::Framework is entirely by convention. You don't have to use Class::DBI
if you don't want to. In fact, you don't have to use a database if you don't want to.
This section assumes that you are making an application similar to the Example application.
CAF uses Class::DBI::Loader
to detect automatically your database schema.
CAF Project database classes typically provide a setup_tables
subroutine.
This subroutine is configured to run at the 'database_init' phase by registering a callback with CGI::Application
:
sub import {
my $caller = caller;
$caller->new_hook('database_init');
$caller->add_callback('database_init', \&setup_tables);
}
All this code does is instruct CAF to run the setup_tables
subroutine at a specific point at the beginning of the request cycle. It happens after the configuration files are loaded, but before the logging system is initialized.
This means that your setup_tables
subroutine code has access to the application configuration:
my $config = CGI::Application::Plugin::Config::Context->get_current_context;
You can then pull the database connection info from your config file:
sub setup_tables {
my $config = CGI::Application::Plugin::Config::Context->get_current_context;
my $db_config = $config->{'db_exmple'};
my $loader = Class::DBI::Loader->new(
debug => 0,
dsn => $db_config->{'dsn'},
user => $db_config->{'username'},
password => $db_config->{'password'},
namespace => __PACKAGE__,
relationships => 1,
);
}
This assumes that you have the something like the following section in your framework.conf
:
<db_example>
dsn = DBI:mysql:database=example
username = rdice
password = seekrit
</db_example>
That's a complete mininimal database configuration. Class::DBI::Loader
will automatically create CDBI classes in the current namespace: one class per table in your database.
In the case of the Example apps, it means that you can say:
my $first_user = CDBI::Example::example::Users->retrieve(1);
The example apps have a bit more code than the above. For instance, instead of letting Class::DBI::Loader automatically figure out the relationships, the Example project defines them manually:
CDBI::Example::example::Users->has_many( albums => 'CDBI::Example::example::UserAlbum');
CDBI::Example::example::Artist->has_many( albums => 'CDBI::Example::example::Album' );
CDBI::Example::example::Artist->has_many( songs => 'CDBI::Example::example::Song' );
It also provides some compatibility with persistent environments like mod_perl
, by only running the setup_tables
sub once per process:
my $Already_Setup_Tables;
sub setup_tables {
return if $Already_Setup_Tables;
# set up the tables here....
$Already_Setup_Tables = 1;
}
DATABASE CONFIGURATION
CGI::Application::Framework
's configuration system allows you to change configuration settings based on the runtime context of your applications.
If you want to have a different database connection for different applications you have a couple of strategies available to you.
Database Config - Per Project
After setting up your database in the previous section, your site-wide framework.conf file should contain a section like the following:
<db_example>
dsn = DBI:mysql:database=example
username = rdice
password = seekrit
</db_example>
The example
in the section name db_example
means that this section applies only to the Example project.
To add a database connection for another project, e.g. named Finance
, add another section:
<db_finance>
dsn = DBI:Pg:dbname=finance
username = rdice
password = sooper-seekrit
</db_finance>
(Note that the names of these database sections is somewhat conventional; you can override these names in your application's database modules.)
Since these are project-specific configurations, you are quite free to put them in their respective project framework.conf files. That is, you can put the <db_example> section in:
projects/Example/framework.conf
And you can put the <db_finance> section in:
projects/Finance/framework.conf
Or you can keep them both in the site-wide file:
projects/framework.conf
It's up to you how you choose to organize your configuration.
Database Config - Per Application
If you have two applications in the same project that each need a different database handle, then you can do this in one of two ways. The first option is to move the database configuration into the application-specific framework.conf:
projects/Finance/receivable/framework.conf
<db_finance>
dsn = DBI:Pg:dbname=receivables
username = abbot
password = guessme
</db_finance>
projects/Finance/payable/framework.conf
<db_finance>
dsn = DBI:mysql:database=payables
username = costello
password = letmein
</db_finance>
The other option is to use the URL matching and Application matching features of the underlying Config::Context
system. For instance:
projects/framework.conf
<LocationMatch receiveables>
<db_finance>
dsn = DBI:Pg:dbname=receivables
username = abbot
password = guessme
</db_finance>
</LocationMatch>
<LocationMatch payables>
<db_finance>
dsn = DBI:mysql:database=payables
username = costello
password = letmein
</db_finance>
</LocationMatch>
For more information on the <LocationMatch> directive and other run-time configuration matching features, see the documentation for CGI::Application::Plugin::Config::Context:
http://search.cpan.org/dist/CGI-Application-Plugin-Config-Context/lib/CGI/Application/Plugin/Config/Context.pm
Database Config - Per Site
If you want to have multiple Apache virtual hosts running the same CGI::Application::Framework
applications, then you can use the site matching features of the configuration system:
projects/framework.conf
<Site CREDIT>
<db_finance>
dsn = DBI:Pg:dbname=receivables
username = abbot
password = guessme
</db_finance>
</Site>
<Site DEBIT>
<db_finance>
dsn = DBI:mysql:database=payables
username = costello
password = letmein
</db_finance>
</Site>
To make this work, you will have to set an environment variable in the <Virtualhost>
section in your Apache httpd.conf
<VirtualHost *>
ServerName www.wepayu.com
SetEnv SITE_NAME DEBIT
# .... other per-host configuration goes here
</VirtualHost>
<VirtualHost *>
ServerName www.youpayus.com
SetEnv SITE_NAME CREDIT
# .... other per-host configuration goes here
</VirtualHost>
If you are mixing and matching databases, and you are running under a persistent environment such as mod_perl, then you must make sure that all of the schemas of all the databases you are using are identical as far as Class::DBI
is concerned. In practice that means that the following items must be the same across databases:
* table names
* column names
* which columns are primary keys
Other database details (such as how columns are indexed) may safely differ from database to database.
DATABASE INSTALLATION
If you already have a database set up and you don't need to load the example data for the Example applications to work, then you can skip this section.
The Framework and its example programs support many databases. In theory, any database that has a DBD::*
driver and a Class::DBI::*
subclass module is supported. This distribution contains explicit support for MySQL
, PostgreSQL
and SQLite
. There are instructions for setting up each of these databases below.
If you like you can use more than one of these databases at the same time. See "Using multiple database configurations" below.
Database Installation - MySQL
This is how to create a MySQL database that works with the Example applications.
In the framework/sql
directory, you will find a file called caf_example.mysql
. First, this must be loaded into the MySQL database. As the root user, type:
# cd framework/sql
# mysql < caf_example.mysql
This will create the "example" database and one table with a few pre-populated rows, "users", and a bunch of other empty tables.
You will want the web application to be able to access the "example" database as a non-root user, so you need to grant access to the database. Do the following
# mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 28 to server version: 4.0.21-log
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> GRANT ALL PRIVILEGES ON example.* TO
-> 'some_username'@'localhost' IDENTIFIED BY 'a_password';
Obviously, pick "some_username"
and "a_password"
that is appropriate to your situation. If you are doing this for test purposes then perhaps you can just use the username of your regular Unix user account and set an empty password. Also, if you want the user to have more privileges than just these you can modify this statement as appropriate. See:
http://dev.mysql.com/doc/mysql/en/MySQL_Database_Administration.html
Item 5.6, "MySQL User Account Management", has information regarding how to set up your grant statement.
Whatever you chose for some_username and a_password you must place these into the configuration in your top-level framework.conf file:
<db_example>
dsn = DBI:mysql:database=example
username = rdice
password = seekrit
</db_example>
Note that other databases will require their own <db_OTHER>...</db_OTHER> configuration blocks. More about this later.
For more information on the format of the 'dsn' parameter, consult the DBD::mysql documentation:
http://search.cpan.org/~rudy/DBD-mysql/lib/DBD/mysql.pm
The caf_example.mysql file does not contain all of the data needed in order to populate the database with seed data for all of the example programs of the Framework. To load the rest of the data, do the following:
# cd framework/sql/
# perl ./load_music_data.pl music_info.csv
This data is stored in a separate file and comes with its own loading program, so that you can see more examples of how the CDBI modules is used to accomplish real-life tasks. Inspect the contents of the load_music_data.pl
file to see how it works.
Database Installation - PostgreSQL
This is how to create a PostgreSQL database that works with the Example applications.
First you must create the example database.
Connect to the postgres database as the postgres user:
$ psql -U postgres template1
Turn off history recording for lines beginning with a space:
template1=# \set HISTCONTROL ignoreboth
Add the example user (begin the line with a space so the password is not recorded in the history):
template1=# CREATE USER some_username WITH password 'a_password' CREATEDB;
Obviously, pick "some_username"
and "a_password"
that is appropriate to your situation. If you are doing this for test purposes then perhaps you can just use the username of your regular Unix user account and set an empty password.
Quit the psql shell:
template1=# \q
Start the psql shell again as the new user:
$ psql -U some_username template1
Create the 'example' database:
template1=> CREATE DATABASE example;
template1=> \q
If you want, you can prevent the user from creating additional databases:
$ psql -U postgres template1
template1=# ALTER USER some_username NOCREATEDB;
template1=# \q
Postgres is often configured to not require passwords from local users (including the postgres superuser).
If you are instaling on a public server, it is a good idea to require passwords.
Do this by editing the ~postgres/data/pg_hba.conf file (as the root user) and changing the lines from 'trust' to either 'md5' or 'crypt':
local all crypt
host all 127.0.0.1 255.255.255.255 crypt
Next, import the database schema.
In the framework/sql directory, you will find a file called caf_example.pgsql
. Load this into the PostgreSQL database. Type:
psql -U some_user -f caf_example.pgsql example
This will create the example
database and one table with a few pre-populated rows, users
, and a bunch of other empty tables.
Whatever you chose for some_username and a_password you must place these into the configuration in your top-level framework.conf file:
<db_example>
dsn = DBI:Pg:dbname=example
username = rdice
password = seekrit
</db_example>
For more information on the format of the 'dsn' parameter, consult the DBD:Pg
documentation:
http://search.cpan.org/~dbdpg/DBD-Pg-1.40/Pg.pm
The caf_example.pgsql
file does not contain all of the data needed in order to populate the database with seed data for all of the example programs of the Framework. To load the rest of the data, do the following:
# cd framework/sql/
# ./load_music_data.pl music_info.csv
This data is stored in a seperate file and comes with its own loading program, so that you can see more examples of how the CDBI modules is used to accomplish real-life tasks. Inspect the contents of the load_music_data.pl
file to see how it works.
Database Installation - SQLite
This is how to create a SQLite database that works with the Example applications.
SQLite is a complete SQL database contained in a DBD
driver. This means you can use it on machines that aren't running a database server.
Each SQLite database is contained in its own file. Database permissions are managed at the filesystem level. Both the file and the directory that contains it must be writable by any users that want to write any data to the database.
The SQLite database and directory should have been created by the CAF installation script. However these instructions also apply to SQLite databases you create for other projects.
Create a directory to contain the SQLite databases:
$ mkdir /home/rdice/Framework/sqlite
Change its permissions so that it is writeable by the group the webserver runs under:
# chown .web /home/rdice/Framework/sqlite
# chmod g+w /home/rdice/Framework/sqlite
Add the group "sticky" bit so that files created in this directory retain the group permissions:
# chmod g+s /home/rdice/Framework/sqlite
Now import the example database shema.
SQLite does not come with a command line shell. Instead, use the dbish program which is installed as part of the DBI::Shell
module.
dbish --batch dbi:SQLite:dbname=/home/rdice/Framework/sqlite/sqlite_db < caf_example.sqlite
This will create the example
database and one table with a few pre-populated rows, users
, and a bunch of other empty tables.
Whatever you chose for some_username and a_password you must place these into the configuration in your top-level framework.conf file:
<db_example>
dsn = DBI:SQLite:dbname=/home/rdice/Framework/sqlite
username = rdice
password = seekrit
</db_example>
For more information on the format of the 'dsn' parameter, consult the DBD::SQLite
documentation:
http://search.cpan.org/~msergeant/DBD-SQLite-1.08/lib/DBD/SQLite.pm
The caf_example.sqlite file does not contain all of the data needed in order to populate the database with seed data for all of the example programs of the Framework. To load the rest of the data, do the following:
# cd framework/sql/
# perl ./load_music_data.pl music_info.csv
This data is stored in a seperate file and comes with its own loading program, so that you can see more examples of how the CDBI modules is used to accomplish real-life tasks. Inspect the contents of the load_music_data.pl
file to see how it works.
AUTHOR
The primary author of CGI::Application::Framework is Richard Dice, <rdice@pobox.com>
, though Michael Graham is right up there, too. (Most of Michael's CAP::* modules created over the past few months have been the result of refactoring code out of CAF and putting it online in chunks small and modular enough to be used by other CGI::App programmers and their applications.)
BUGS
Please report any bugs or feature requests to bug-cgi-application-framework@rt.cpan.org
, or through the web interface at http://rt.cpan.org. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
ACKNOWLEDGEMENTS
Code contributions and suggestions have some from the following people:
* Michael Graham
- above all, for looking after CAF circa Feb - Sept 2005 while
Richard had his head deep deep up and into YAPC's assembly
('til June) and recovery (July - Sept) and the myriad
technical improvements done throughout that time, such as...
- support for multiple databases (e.g. PostgreSQL, SQLite)
- support for multiple Template backends (e.g. Template::Toolkit,
Petal)
- the component embedding system
- the config system via CGI::Application::Plugin::Config::Context
- the run_app system
- the test suite
- documentation support
- Log::Dispatch support
- per-request database configuration under mod_perl
- the Module::Build-based installer
- extensive discussions of the system
* Alex Spenser reorganized the example applications, made them xhtml
compliant and added stylesheets and graphics. He also helped
develop the logo.
* Many thanks to Jesse Erlbaum (CGI::Application creator and past
maintainer) and Mark Stosberg (current CGI::Application maintainer
and overseer, as well as CGI::Application::Plugin::ValidateRM
author and Data::FormValidator maintainer)
* Thanks to Cees Hek for CAP::Log::Dispatch, for ideas, and for
discussions of the architecture.
* Thanks also to Sam Tregar for HTML::Template
* Thanks to the many users on the CGI::Application mailing list for
feedback and support.
* cgi-application-framework-support@dice-con.com
* Rick Delaney (for making numerous suggestions regarding
simplifications to the creation of templates within run-modes)
and G. Matthew Rice (for this and lots more) at LPI...
* Thanks to the LPI, the Linux Professional Institute (http://www.lpi.org/),
for helping support the development of this project. (But do not
approach LPI for technical support, as they won't know how to
help. They are mentioned here because they are the fine sponsors
of this project and users of this technology.)
COPYRIGHT & LICENSE
Copyright 2005 Richard Dice, All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.