Perl Beginners - Addressbook Tutorial Step 3 - Creating perl classes and our first cgi

Table of Contents | Step 2 | Step 4

Overview

First, let's go over some locations for our code.

As I stated in the Introduction, I run RedHat Linux and Apache. Therefore, the directories I use will be specific to that type of configuration. Solaris and Windows users may have to make some adjustments.

My Perl handlers and utility classes will be stored in


  /usr/lib/perl5/5.6.0/PEACE/AddressBook/
  

and the base class will be stored in


  /usr/lib/perl5/5.6.0/PEACE/
  

The server root will contain these directories:


  $ ls -l
  total 12
  drwxrwxr-x    2 fliptop fliptop     4096 Jun 10 11:35 cgi-bin
  drwxrwxr-x    2 fliptop fliptop     4096 Jun 10 11:34 html
  drwxrwxr-x    2 fliptop fliptop     4096 Jun 10 11:30 templates
  $ 
  

Obviously, all my cgi scripts will be in the cgi-bin directory, and I'll use the templates directory to store the HTML::Template files. Plain HTML files are stored in the html directory.

The base class

The base class contains any variables and methods that are common to all the other classes for this project. So let's ask ourselves what each cgi request will need:

  • a way to get a connection to the database, also known as a handle; and
  • a root directory where the HTML::Template files are stored.
  • Later, we'll include the root directory where our uploaded images will be stored.

    Here is a base class to start off with, AddressBook.pm:

    
      package PEACE::AddressBook;
      # /usr/lib/perl5/5.6.0/PEACE/AddressBook.pm - base class for addressbook
      require 5.6.0;
      use strict;
      no warnings qw{once};
    
      our $VERSION = do { my @r = (q$Revision: 1.1.1.1 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; 
      # must be all one line, for MakeMaker
      our $DBI_URL = q{DBI:Pg:dbname=addressbook;host=localhost};
      our $DBI_USERNAME = 'username';  # use your own username here
      our $DBI_PASSWORD = 'password';  # use your own password here
      our $DBI_OPTIONS = {RaiseError => 1, ChopBlanks => 1, AutoCommit => 1};
      our @DBI_CONNECT_ARGS = ($DBI_URL, $DBI_USERNAME, $DBI_PASSWORD, $DBI_OPTIONS);
      our $HTML_TEMPLATE_ROOT = q{/path/to/server/root/templates};
      1;
      __END__
      

    Let's look at each part of this class.

    
      package PEACE::AddressBook;
      require 5.6.0;
      use strict; 
      no warnings qw{once};
      

    Each class will begin with the package declaration, creating a namespace where the the package's variables, methods, etc. will reside. require 5.6.0 and use strict are two pragmas that say, "This code is meant for Perl version 5.6.0 or higher, and I want to eliminate sloppy constructs." use strict will force us to use my or our when declaring variables. no warnings shuts up "Variable $x used only once.." warnings.

    
      our $VERSION = do { my @r = (q$Revision: 1.1.1.1 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };
      # must be all one line, for MakeMaker
      

    Although not necessary, declaring a $VERSION variable can be useful. For example, try typing this at a command prompt:

    
      $ perl -MDBI -e 'print $DBI::VERSION, "\n"'
      1.14
      $
      

    Without the $VERSION declaration, it will print a blank line. The $VERSION declaration also is useful if you're using CVS, which is beyond the scope of this tutorial.

    
      our $DBI_URL = q{DBI:Pg:dbname=addressbook;host=localhost};
      our $DBI_USERNAME = 'username';  # use your own username here
      our $DBI_PASSWORD = 'password';  # use your own password here
      our $DBI_OPTIONS = {RaiseError => 1, ChopBlanks => 1, AutoCommit => 1};
      our @DBI_CONNECT_ARGS = ($DBI_URL, $DBI_USERNAME, $DBI_PASSWORD, $DBI_OPTIONS);
      

    Here we specify the arguments necessary to get a handle to the database. Notice I am using DBI:Pg; MySQL users should use DBI:mysql. Use your own username and password to make the database connection. $DBI_OPTIONS is a reference to a hash of arguments that tell DBI how to behave. Read the documentation for DBI for more information on these arguments. You can do that by typing perldoc DBI. Using @DBI_CONNECT_ARGS makes DBI connect calls easier; more on that later.

    
      our $HTML_TEMPLATE_ROOT = q{/path/to/server/root/templates};
      

    Here we specify where the HTML::Template files will be located.

    
      1;
      __END__
      

    All classes must return TRUE, so we include the 1;. The token __END__ (or alternatively, a Control-D or Control-Z character) may be used to indicate the logical end of the script before the real end-of-file.1 We'll put our comments after it (later).

    Our first cgi

    According to the rules in the Introduction, the cgi should be flexible, easy to edit, separate from the html, and should fit on one page. Here's what we'll need the cgi to do:

  • get references to CGI and Handler objects;
  • decide which Handler method to call based on an action value;
  • put the action's key-value pairs into an array;
  • call the action's method, passing it the array;
  • set the output's Content-type; and
  • send the output.
  • We'll need just one cgi to process all the non-restricted applications. Here's the code for handler.cgi:

    
      #!/usr/bin/perl -w
      # /path/to/server/root/cgi-bin/handler.cgi - cgi script to process non-restricted addressbook requests
      use strict;
      use PEACE::AddressBook::Handler;
      use CGI;
      
      my $cgi = new CGI;
      my $action = $cgi->param('action') || 'start';
      my $c = PEACE::AddressBook::Handler->new(
        action        => $action,
        cgi           => $cgi
      );
    
      my $req_params = $c->required_parameters();
      my @args;
    
      foreach my $key (keys(%$req_params)) {
        my @values = $cgi->param($key);
      
        if (scalar(@values) == 1) {
          (push(@args, $key => $values[0]))
        }
        else {
          map { push(@args, $key => $_) } @values;
        } 
      }
      
      $c->$action(@args); 
      print "Content-type:  text/html\n\n";
      $c->sendoutput();
      exit();
      

    Let's examine this script one section at a time.

    
      #!/usr/bin/perl -w
      use strict;
      use PEACE::AddressBook::Handler;
      use CGI;
      

    The -w switch on the shebang line tells Perl to turn on warnings. We'll use warnings while the code is in development. Next we use the Perl handler (which we have not written yet), and then we use CGI.

    
      my $cgi = new CGI;
      my $action = $cgi->param('action') || 'start';
      my $c = PEACE::AddressBook::Handler->new(
        action        => $action,
        cgi           => $cgi
      );
      

    First we construct a reference to a new CGI object and read the action parameter value passed to the script (or set it to 'start' if omitted). Then we construct a reference to a new Handler object, passing in a hash with the action and the reference to the CGI object.

    
      my $req_params = $c->required_parameters();
      my @args;
    
      foreach my $key (keys(%$req_params)) {
        my @values = $cgi->param($key);
    
        if (scalar(@values) == 1) {
          (push(@args, $key => $values[0]))
        }
        else {
          map { push(@args, $key => $_) } @values;
        }
      }
      

    Here we get a reference to a required parameters hash. We'll store the arguments to pass to the action method in @args. Then, for every key in the required parameters hash, we'll put the CGI parameter values passed in each form element name into an array. If the array contains just one value, we'll push the parameter key and value into @args. If the array contains more than one value, we'll push a parameter key and a value onto @args for every element in the array.

    Why go through all of this trouble? Let's assume we're executing an action method to search for a particular record by the record's ID. The URL may look like this:

    
      /cgi-bin/handler.cgi?action=search&record_id=123
      

    The @args array will contain the values 'record_id', '123'. But if the search method is capable of looking for more than one record at a time, the URL may be as such:

    
      /cgi-bin/handler.cgi?action=search&record_id=123&record_id=321&record_id=213
      

    In this case, the @args array will contain 'record_id', '123', 'record_id', '321', 'record_id', '213'.

    
      $c->$action(@args);
      print "Content-type:  text/html\n\n";
      $c->sendoutput();
      exit();
      

    Finally, we call the action method, passing in the arguments. Then we print the obligatory Content-type header and call the Handler's sendoutput method.

    You may notice that when this cgi is pre-compiled (by typing perl -c handler.cgi) that you get an error stating Perl can't locate Handler.pm. That's because we haven't written it yet, which comes next.

    Coming Next: Step 4 - The Perl handler (and a table of contents for the tutorial)


    1 - Wall, Larry, et. al. (2000). Programming Perl, 3rd Edition. Sebastopol, CA: O'Reilly & Associates, Inc., p. 68.

    Copyright © 2001 by Peace Computer Systems