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

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 &param 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.