CGI::Application::Framework - Fully-featured MVC web application platform
Version 0.26
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!
A CGI::Application::Framework project has the following layout:
CGI::Application::Framework
/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
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.
Projects
applications
MyProj
myapp
The actual CGI script (app.cgi) is tiny. It looks like this:
app.cgi
#!/usr/bin/perl use strict; use CGI::Application::Framework; use CGI::Carp 'fatalsToBrowser'; CGI::Application::Framework->run_app( projects => '../caf/projects', );
Your application module (myapp.pm) looks like a standard CGI::Application, with some extra features enabled.
myapp.pm
CGI::Application
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, }); }
The template is named (by default) after the run mode that called it. In this case, it's runmode_one.html:
runmode_one.html
<html> <body> <h1>Welcome</h1> Hello there, <!-- TMPL_VAR NAME="name" --> </body> </html>
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; }
By convention, the database classes are also split into Project and application. First the project level CDBI::MyProj:
CDBI::MyProj
package CDBI::MyProj; use base qw( CGI::Application::Framework::CDBI ); use strict; use warnings; 1;
Next, the application-specific CDBI::Project::app
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;
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.
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:
Config::Context
Config::General
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>
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)
HTML::Template
HTML::Template::Expr
Template::Toolkit
Petal
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)
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.
rm
some_runmode
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
All the magic happens in the run_app method. This method does a lot of magic in one go:
run_app
* 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.
Any extra parameters to pass as the PARAMS option to the application's new method. undefined by default.
PARAMS
new
A CGI query object to pass as the QUERY option to the application's new method. undefined by default
QUERY
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.
Where the templates common to all apps in this project are stored. Defaults to:
$projects/$project_name/common-templates
Where the application Perl modules are stored. Defaults to:
$projects/$project_name/applications/$app_name/
Where the application-specific template files are. Defaults to:
$app_dir/templates
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.
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.
AnyTemplate
Currently supported templating systems include HTML::Template, HTML::Template::Expr, Template::Toolkit and Petal.
The syntax is pretty flexible. Pick a style that's most comfortable for you.
$self->template->process('edit_user', \%params);
or (with slightly less typing):
$self->template->fill('edit_user', \%params);
my $template = $self->template->load('edit_user'); $template->param('foo' => 'bar'); $template->output;
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.
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>
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
<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.
TemplateOptions
By default, your application templates are stored in the templates subdirectory of your application directory:
templates
/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
By default, project templates are stored in the common-templates subdirectory of your project directory:
common-templates
projects/ MyProj/ common-templates/ # login and other common # templates go here
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 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.
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.
some_run_mode
CGI::Application::Plugin::AnyTemplate
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
$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);
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
my $url = $self->make_self_url;
my $url = $self->make_link(url => $self->query->url); my $url = $self->make_link(url => $other_url);
Options for make_link
The base URL (without query string). Defaults to the URL for the current application.
This is just a utility method to perform an HTTP redirect:
$self->redirect($self->make_link(url => $other_url));
You can send logging messages via the log method:
log
$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).
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
min_level = info
Then the handler will also log all info and notice messages as well. If you change it to:
info
notice
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:
$self
Logs your message, indicating the caller, and then dies with the same message:
$self->log_croak("Something bad happened");
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:
Logs your message, indicating the caller and then gives off a warning with the same message:
$self->log_carp("Something strange is happening");
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");
Currently, all users of a CAF application have to login in order to use the system. This will change in a future release.
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.
If you want, you can override the following runmodes. A good place to do this is in your project module.
This runmode presents the login screen.
This runmode presents the login screen with the user's name already filled in.
This runmode displays "invalid session" template.
This runmode displays "invalid checksum" template.
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.
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:
$query
(0, undef) --> Unknown user (0, $user) --> user was found, incorrect password given (1, $user) --> user was found, password given correct
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:
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.
login.html
The specifics of this should match the needs of your relogin.html form-displaying HTML::Template.
relogin.html
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.
Similar to _login_failed_errors but for the relogin.html
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.
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.
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.
_initialize_session
This is used to provide template variables to the "login" form.
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.
CGI::Application::Plugin::Config::Context
Config::Scoped
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:
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.
The following methods are provided as hooks for you to override the configuration system if you need to do that.
This sub returns the arguments passed to $self->conf->init. By providing your own arguments, you can change the config backend from Config::General to a different backend.
$self->conf->init
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.
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.
config_name
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 your cgiapp_init method.
$self->config->context
cgiapp_init
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.
$self->config_file
This is the name of the database config name. By default it is undef, the same as $self->config_name. See config_name, above.
$self->config_name
The AnyTemplate system also allows for multiple simultaneous configurations. By default the template_config_name is undef so you can just say:
template_config_name
undef
$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.
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 is caf_system_templates.
caf_system_templates
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.
Class::DBI
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.
Class::DBI::Loader
CAF Project database classes typically provide a setup_tables subroutine.
setup_tables
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:
mod_perl
my $Already_Setup_Tables; sub setup_tables { return if $Already_Setup_Tables; # set up the tables here.... $Already_Setup_Tables = 1; }
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.
After setting up your database in the previous section, your site-wide framework.conf file should contain a section like the following:
The example in the section name db_example means that this section applies only to the Example project.
example
db_example
To add a database connection for another project, e.g. named Finance, add another section:
Finance
<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.
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
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>
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.
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.
DBD::*
Class::DBI::*
MySQL
PostgreSQL
SQLite
If you like you can use more than one of these databases at the same time. See "Using multiple database configurations" below.
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:
framework/sql
caf_example.mysql
# 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:
"some_username"
"a_password"
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:
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.
load_music_data.pl
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:
caf_example.pgsql
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.
users
<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:
DBD:Pg
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.
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.
DBD
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.
DBI::Shell
dbish --batch dbi:SQLite:dbname=/home/rdice/Framework/sqlite/sqlite_db < caf_example.sqlite
<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:
DBD::SQLite
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:
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.)
<rdice@pobox.com>
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.
bug-cgi-application-framework@rt.cpan.org
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 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.
To install CGI::Application::Framework, copy and paste the appropriate command in to your terminal.
cpanm
cpanm CGI::Application::Framework
CPAN shell
perl -MCPAN -e shell install CGI::Application::Framework
For more information on module installation, please visit the detailed CPAN module installation guide.