The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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:

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's new method. undefined by default.

query

A CGI query object to pass as the QUERY option to the application's new 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;
    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 from Config::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 your cgiapp_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 the template_config_name is undef 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 is caf_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.