NAME
Class::CGI - Fetch objects from your CGI object
VERSION
Version 0.20
SYNOPSIS
use Class::CGI
handlers => {
customer_id => 'My::Customer::Handler'
};
my $cgi = Class::CGI->new;
my $customer = $cgi->param('customer_id');
my $name = $customer->name;
my $email = $cgi->param('email'); # behaves like normal
if ( my %errors = $cgi->errors ) {
# do error handling
}
DESCRIPTION
For small CGI scripts, it's common to get a parameter, untaint it, pass it to an object constructor and get the object back. This module would allow one to to build Class::CGI
handler classes which take the parameter value, automatically perform those steps and just return the object. Much grunt work goes away and you can get back to merely pretending to work.
ALPHA CODE
Note that this work is still under development. It is not yet suitable for production work as the interface may change. Join the mailing list in the SUPPORT section if you would like to influence the future direction of this project.
EXPORT
None.
BASIC USE
The simplest method of using Class::CGI
is to simply specify each form parameter's handler class in the import list:
use Class::CGI
handlers => {
customer => 'My::Customer::Handler',
sales => 'Sales::Loader'
};
my $cgi = Class::CGI->new;
my $customer = $cgi->param('customer');
my $email = $cgi->param('email');
# validate email
$customer->email($email);
$customer->save;
Note that there is no naming requirement for the handler classes and any form parameter which does not have a handler class behaves just like a normal form parameter. Each handler class is expected to have a constructor named new
which takes the raw form value and returns an object corresponding to that value. All untainting and validation is expected to be dealt with by the handler. See "WRITING HANDLERS".
If you need different handlers for the same form parameter names (this is common in persistent environments) you may omit the import list and use the handlers
method.
LOADING THE HANDLERS
When the handlers are specified, either via the import list or the handlers()
method, we verify that the handler exists and croak()
if it is not. However, we do not load the handler until the parameter for that handler is fetched. This allows us to not load unused handlers but still have a semblance of safety that the handlers actually exist.
METHODS
new
my $cgi = Class::CGI->new(@args);
This method takes the same arguments (if any) as CGI::Simple's constructor.
handlers
use Class::CGI;
my $cust_cgi = Class::CGI->new;
$cust_cgi->handlers(
customer => 'My::Customer::Handler',
);
my $order_cgi = Class::CGI->new($other_params);
$order_cgi->handlers(
order => 'My::Order::Handler',
);
my $customer = $cust_cgi->param('customer');
my $order = $order_cgi->param('order');
$order->customer($customer);
my $handlers = $cgi->handlers; # returns hashref of current handlers
Sometimes we get our CGI parameters from different sources. This commonly happens in a persistent environment where the class handlers for one form may not be appropriate for another form. When this occurs, you may set the handler classes on an instance of the Class::CGI
object. This overrides global class handlers set in the import list:
use Class::CGI handlers => {
customer => "Some::Customer::Handler",
order => "My::Order::Handler"
};
my $cgi = Class::CGI->new;
$cgi->handlers( customer => "Some::Other::Customer::Handler" );
In the above example, the $cgi
object will not use the Some::Customer::Handler
class. Further, the "order" handler will not be available. Setting hanlders on an makes the global handlers unavailable. If you also needed the "order" handler, you need to specify that in the &handlers
method.
If called without arguments, returns a hashref of the current handlers in effect.
profiles
$cgi->profiles($profile_file, @use);
If you prefer, you can specify a config file listing the available Class::CGI
profile handlers and an optional list stating which of the profiles to use. If the @use
list is not specified, all profiles will be used. Otherwise, only those profiles listed in @use
will be used. These profiles are used on a per instance basis, similar to &handlers
.
See "DEFINING PROFILES" for more information about the profile configuration file.
param
use Class::CGI
handlers => {
customer => 'My::Customer::Handler'
};
my $cgi = Class::CGI->new;
my $customer = $cgi->param('customer'); # returns an object, if found
my $email = $cgi->param('email'); # returns the raw value
my @sports = $cgi->param('sports'); # behaves like you would expect
If a handler is defined for a particular parameter, the param()
calls the new()
method for that handler, passing the Class::CGI
object and the parameter's name. Returns the value returned by new()
. In the example above, for "customer", the return value is essentially:
return My::Customer::Handler->new( $self, 'customer' );
raw_param
my $id = $cgi->raw_param('customer');
This method returns the actual value of a parameter, ignoring any handlers defined for it.
args
$cgi->args('customer', \@whatever_you_want);
my $args = $cgi->args($param);
This method allows you to pass extra arguments to a handler. Specify the name of the parameter for which you wish to provide the arguments and then provide a single argument (it may be a reference). In your handler, you can access it like this:
package Some::Handler;
sub new {
my ( $class, $cgi, $param ) = @_;
my $args = $cgi->args($param);
...
}
errors
if ( my %errors = $cgi->errors ) {
...
}
Returns exceptions thrown by handlers, if any. In scalar context, returns a hash reference. Note that these exceptions are generated via the overloaded ¶m
method. For example, let's consider the following:
use Class::CGI
handlers => {
customer => 'My::Customer::Handler',
date => 'My::Date::Handler',
order => 'My::Order::Handler',
};
my $cgi = Class::CGI->new;
my $customer = $cgi->param('customer');
my $date = $cgi->param('date');
my $order = $cgi->param('order');
if ( my %errors = $cgi->errors ) {
# do error handling
}
If errors are generated by the param statements, returns a hash of the errors. The keys are the param names and the values are whatever exception the handler throws. Returns a hashref in scalar context.
If no errors were generated, this method simply returns. This allows you to do this:
if ( $cgi->errors ) { ... }
If any of the $cgi->param
calls generates an error, it will not throw an exception. Instead, control will pass to the next statement. After all $cgi->param
calls are made, you can check the &errors
method to see if any errors were generated and, if so, handle them appropriately.
This allows the programmer to validate the entire set of form data and report all errors at once. Otherwise, you wind up with the problem often seen on Web forms where a customer will incorrectly fill out multiple fields and have the Web page returned for the first error, which gets corrected, and then the page returns the next error, and so on. This is very frustrating for a customer and should be avoided at all costs.
clear_errors
$cgi->clear_errors;
Deletes all errors returned by the &errors
method.
add_error
$cgi->add_error( $param, $error );
This method add an error for the given parameter.
add_missing
$cgi->add_missing( $param, $optional_error_message );
Helper function used in handlers to note that a parameter is "missing". This should only be used for "required" parameters. Calling this method with a non-required parameter is a no-op. See the required and is_required
methods.
Missing parameters will be reported via the errors and is_missing_required methods.
is_missing_required
if ( $cgi->is_missing_required( $param ) ) {
...
}
Returns a boolean value indicating whether or not a required parameter is missing. Always return false for parameters which are not required.
Note that this value is set via the add_missing method.
error_encoding
$cgi->error_encoding( $unsafe_characters );
Error messages must be properly escaped for display in HTML. We use HTML::Entities
to handle the encoding. By default, this encodes control characters, high bit characters, and the "<", "&", ">", "'" and """ characters. This should suffice for most uses.
If you need to specify a different set of characters to encode, you may set them with this method. See the encode_entities
documentation in HTML::Entities for details on the $unsafe_characters
.
required
$cgi->required(@required_parameters);
Allows you to set which parameters are required for this Class::CGI
object. Any previous "required" parameters will be cleared.
is_required
if ( $cgi->is_required($param) ) {
...
}
Generally used in handlers, this method returns a boolean value indicating whether or not a given parameter is required.
WRITING HANDLERS
A basic handler
Handlers are usually pretty easy to write. There are a few simple rules to remember.
Inherit from Class::CGI::Handler.
Provide a method named
handle
which takes$self
as an argument.Return whatever value you want.
For virtual parameters, override the
has_param
method.
And that's pretty much it. See the Class::CGI::Handler documentation for what methods are available to call on $self
. The ones which will probably always be used are the cgi
and param
methods.
Writing a handler is a fairly straightforward affair. Let's assume that our form has a parameter named "customer" and this parameter should point to a customer ID. The ID is assumed to be a positive integer value. For this example, we assume that our customer class is named My::Customer
and we load a customer object with the load_from_id()
method. The handler might look like this:
package My::Customer::Handler;
use base 'Class::CGI::Handler';
use My::Customer;
sub handle {
my $self = shift;
my $cgi = $self->cgi;
my $param = $self->param;
my $id = $cgi->raw_param($param);
unless ( $id && $id =~ /^\d+$/ ) {
die "Invalid id ($id) for $class";
}
return My::Customer->load_from_id($id)
|| die "Could not find customer for ($id)";
}
1;
Pretty simple, eh?
Using this in your code is as simple as:
use Class::CGI
handlers => {
customer => 'My::Customer::Handler',
};
If Class::CGI
is being used in a persistent environment and other forms might have a param named customer
but this param should not become a My::Customer
object, then set the handler on the instance instead:
use Class::CGI;
my $cgi = Class::CGI->new;
$cgi->handlers( customer => 'My::Customer::Handler' );
Important: Note that earlier versions of Class::CGI
listed handlers with names like Class::CGI::Order
. It is recommended that you not use the Class::CGI::
namespace to avoid possibly conflicts with handlers which may be released to the CPAN in this namespace unless you also intend to release your module to the CPAN in this namespace.
A more complex example
As a more common example, let's say you have the following data in a form:
<select name="month">
<option value="01">January</option>
...
<option value="12">December</option>
</select>
<select name="day">
<option value="01">1</option>
...
<option value="31">31</option>
</select>
<select name="year">
<option value="2006">2006</option>
...
<option value="1900">1900</option>
</select>
Ordinarily, pulling all of that out, untainting it is a pain. Here's a hypothetical handler for it:
package My::Date::Handler;
use base 'Class::CGI::Handler';
use My::Date;
sub handle {
my $self = shift;
my $cgi = $self->cgi;
my $month = $cgi->raw_param('month');
my $day = $cgi->raw_param('day');
my $year = $cgi->raw_param('year');
return My::Date->new(
month => $month,
day => $day,
year => $year,
);
}
# because this is a virtual parameter, we must override the has_param()
# method.
sub has_param {
my $self = shift;
return $self->has_virtual_param( date => qw/day month year/ );
}
1;
And in the user's code:
use Class::CGI
handlers => {
date => 'My::Date::Handler',
};
my $cgi = Class::CGI->new;
my $date = $cgi->param('date');
my $day = $date->day;
Note that this does not even require an actual param named "date" in the form. The handler encapsulates all of that and the end user does not need to know the difference.
Virtual parameters
Note that the parameter a user fetches might not exist on the form. In the $cgi->param('date')
example above, there is no "date" parameter. Instead, it's a composite formed of other fields. It's strongly recommended that if you have a handler which uses virtual parameters that you do not use a parameter with the same name. If you must, you can still access the value of the real parameter with $cgi->raw_param('date');
.
Reusing handlers
Sometimes you might want to use a handler more than once for the same set of data. For example, you might want to have more than one date on a page. To handle issues like this, we pass in the parameter name to the constructor so you can know which date you're trying to fetch.
So for example, let's say their are three dates in a form. One is the customer birth date, one is an order date and one is just a plain date. Maybe our code will look like this:
$cgi->handlers(
birth_date => 'My::Date::Handler',
order_date => 'My::Date::Handler',
date => 'My::Date::Handler',
);
One way of handling that would be the following:
package My::Date::Handler;
use base 'Class::CGI::Handler';
use strict;
use warnings;
use My::Date;
sub handle {
my $self = shift;
my $cgi = $self->cgi;
my $param = $self->param;
my $prefix;
if ( 'date' eq $param ) {
$prefix = '';
}
else {
($prefix = $param) =~ s/date$//;
}
my ( $day, $month, $year ) =
grep {defined}
map { $cgi->raw_param($_) } $self->components;
return My::Date->new(
day => $day,
month => $month,
year => $year,
);
}
sub components {
my $self = shift;
my $cgi = $self->cgi;
my $param = $self->param;
my $prefix;
if ( 'date' eq $param ) {
$prefix = '';
}
else {
($prefix = $param) =~ s/date$//;
}
return map { "$prefix$_" } qw/day month year/;
}
sub has_param {
my $self = shift;
return $self->has_virtual_param( $self->param, $self->components );
}
1;
For that, the birthdate will be built from params named birth_day
, birth_month
and birth_year
. The order date would be order_day
and so on. The "plain" date would be built from params named day
, month
, and year
. Thus, all three could be accessed as follows:
my $birthdate = $cgi->param('birth_date');
my $order_date = $cgi->param('order_date');
my $date = $cgi->param('date');
DEFINING PROFILES
Handlers for parameters may be defined in an import list:
use Class::CGI
handlers => {
customer => 'My::Customer::Handler',
order_date => 'My::Date::Handler',
order => 'My::Order::Handler',
};
Creating a profile file
For larger sites, it's not very practical to replicate this in all code which needs it. Instead, Class::CGI
allows you to define a "profiles" file. This is a configuration file which should match the Config::Std
format. At the present time, only one section, "profiles", is supported. This should be followed by a set of colon-delimited key/value pairs specifying the CGI parameter name and the handler class for the parameter. The above import list could be listed like this in the file:
[profiles]
customer: My::Customer::Handler
order_date: My::Date::Handler
order: My::Order::Handler
You may then use the profiles in your code as follows:
use Class::CGI profiles => $location_of_profile_file;
It may be the case that you don't want all of the profiles. In that case, you can list a "use" section for that:
use Class::CGI
profiles => $location_of_profile_file,
use => [qw/ order_date order /];
As with &handlers
, you may find that you don't want the profiles globally applied. In that case, use the &profiles
method described above:
$cgi->profiles( $profile_file, @optional_list_of_profiles_to_use );
DESIGN CONSIDERATIONS
Subclassing CGI::Simple
Because this module is a subclass of CGI::Simple
, all of CGI::Simple
's methods and behaviors should be available. We do not subclass off of CGI
because CGI::Simple
is faster and it's assumed that if we're going the full OO route that we are already using templates. Thus, the CGI
HTML generation methods are not available and should not be needed. This decision may be revisited in the future.
More to the point, CGI.pm, while being faster and more lightweight than most people give it credit for, is a pain to subclass. Further, it would need to be subclassed without exposing the functional interface due to the need to maintain state in Class::CGI
.
Delayed loading
When handlers are specified, either at compile time or setting them on an instance, the existence of the handlers is verified. However, the handlers are not loaded until used, thus reducing memory usage if they are not needed.
In a similar vein, if you choose to use a profile file (see "Creating a profile file"), Config::Std
is used. However, that module is also not loaded unless needed.
Why not Data::FormValidator?
The biggest complaint about CGI::Simple
seems to be that it's "reinventing the wheel". Before you agree with that complaint, see http://www.perlmonks.org/?node_id=543742. Pointy-haired boss summary of that link: you had better reinvent the wheel if you're creating a motorcycle instead of a car.
There's nothing wrong with Data::FormValidator
. It's fast, powerful, and well-proven in its approach. Class::CGI
, in fact, can easily benefit from Data::FormValidator
inside of handler classes. However, the approach we take is fundamentally different. First, instead of learning a list of required hash keys and trying to remember what optional_regexp
, filters
, field_filter_regexp_map
, dependency_groups
and so on do, you just need to know that a handler constructor takes a Class::CGI
instance and the parameter name. Everything else is just normal Perl code, no memorization required.
With Class::CGI
, you can pick and choose what handlers you wish to support for a given piece of code. You can have a global set of handlers to enforce consistency in your Web site or you can have "per page" handlers set up as needed.
TODO
This module should be considered alpha code. It probably has bugs. Comments and suggestions welcome.
AUTHOR
Curtis "Ovid" Poe, <ovid@cpan.org>
SUPPORT
There is a mailing list at http://groups.yahoo.com/group/class_cgi/. Currently it is low volume. That might change in the future.
BUGS
Please report any bugs or feature requests to bug-class-cgi@rt.cpan.org
, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Class-CGI. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
If you are unsure if a particular behavior is a bug, feel free to send mail to the mailing list.
SEE ALSO
This module is based on the philosophy of building super-simple code which solves common problems with a minimum of memorization. That being said, it may not be the best fit for your code. Here are a few other options to consider.
Data::FormValidator - Validates user input based on input profile
HTML::Widget - HTML Widget And Validation Framework
Rose::HTML::Objects - Object-oriented interfaces for HTML
ACKNOWLEDGEMENTS
Thanks to Aristotle for pointing out how useful passing the parameter name to the handler would be.
COPYRIGHT & LICENSE
Copyright 2006 Curtis "Ovid" Poe, all rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.