bradcathey has asked for the wisdom of the Perl Monks concerning the following question:

I'm back with another question on my pilgrimage to Perl wizardry.

I have several forms and pages within a site that are all processed by one larger-sized script and rendered by HTML::Template. The user's choices come from the name and value of the various submit buttons throughout the site. Example:
<input type="submit" name="funct" value="View Books">
or by some hard links, like:
<a href="cgi-bin/script.pl?funct=view%20books>View Books</a>
CGI parses the input values, including $funct, which then directs the processing to one of a number of subroutines.
SWITCH: for ($funct){ /save news/i && do { &savenews; last; }; /save book/i && do { &addbook; last; }; /save observ/i && do { &addobserv; last; }; /save rule/i && do { &addrule; last; }; /update book/i && do { &updatebook; last; }; /update observ/i && do { &updateobserv; last; }; /update rule/i && do { &updaterule; last; }; /edit news/i && do { &editnews; last; }; /edit book/i && do { &editbook; last; }; /edit observ/i && do { &editobserv; last; }; /edit rule/i && do { &editrule; last; }; /add book/i && do { &addbkform; last; }; /add observ/i && do { &addobform; last; }; /add rule/i && do { &addruform; last; }; /list authors/i && do { &listall; last; }; /list titles/i && do { &listall; last; }; /view news/i && do { &viewnews; last; }; /view book/i && do { &viewbooks; last; }; /view observ/i && do { &viewobserv; last; }; /view rule/i && do { &viewrules; last; }; /next/i && do { &viewbooks; last; }; /prev/i && do { &viewbooks; last; }; $funct = ""; } print "Content-type: text/html\n\n"; print $template->output; #display html #end
I left all that "switching" code in there so you could see just how many subs need to be called and how they are called.

Question: (finally!): Is this how, or is this the best way to take a user's selection and direct the processing within the script (assuming one subscribes to the "put-it-all-in-one-script" philosophy)? Is it efficient or a is there a better way? As a web guy I do LOTS of this kind of thing, so I'd like to nip it in the bud.

Thanks!

—Brad
"A little yeast leavens the whole dough."

Replies are listed 'Best First'.
Re: Redirection within a Perl script
by Zaxo (Archbishop) on Dec 04, 2003 at 02:26 UTC

    The for ($funct) { ... } one-time loop is better written as

    { local $_ = $funct; #... }
    but switches are awkward things in perl. They are always a scan over a bunch of conditions. You can only group the most frequent requests first to improve them.

    Another way, tidier I think, is to build a dispatch table of the coderefs,

    my %action = ( 'Save News' => \&savenews, 'Save Book' => \&addbook, 'Save Observ' => \&addobserv, 'Save Rule' => \&addrule, # ... 'Next' => \&viewbooks, 'Prev' => \&viewbooks, );
    Now you can call any action by $action($funct}->() or default(); (for some sub default). I've eliminated the case insensitivity, since the keys ought to be coming from your form.

    Update: runrig++ is wise to suggest checking for existence of the $func key. His trinary op for calling is better than what I showed. I forgot to mention that the CGI::Application module provides a good wrapper for this sort of thing.

    After Compline,
    Zaxo

      Thanks Zaxo. As I mentioned in my reply to runrig, I'm not getting the dispatch tables immediately, but your example helps and I'll go ahead and try it, without understanding it completely, and maybe it will become clearer as I go. Not very orthodox, but I'm probably not the first coder to write something I don't fully understand, and have it work :-)

      Update:
      Googled, found lots of examples, but none as clear as in above replies. So, started with code posted here, and after some minor explosions, got it. Here's a distilled sample that puts it all together. Thanks all!
      #CGI stuff and parsing form here ... my $funct = param('funct'); #button user clicks my %action = ( 'Save News' => \&savenews, 'Add Book' => \&addbook, ); ( $action{$funct} || \&default )->(); sub savenews { print "save news\n"; } sub addbook { print "add book\n"; } sub default { print "not a choice\n"; }

      —Brad
      "A little yeast leavens the whole dough."
Re: Redirection within a Perl script
by runrig (Abbot) on Dec 04, 2003 at 02:17 UTC
    Seeing that you have some repeated subroutines, you might want to use this multiple key dispatch method to map values to subroutines. Then just do something like:
    (exists $hash{$funct} ? $hash{$funct} : \&some_default)->();
    Update: While the trinary operator is overkill in this example, if the script and the dispatch hash were persistent, you would want to do it this way so as to not define any new keys in the hash, and open up a possible source of a memory leak. Also Zaxo++ for pointing out what a regular dispatch hash is, which is more straightforward than the code I linked to (though a regular example is in the parent of the linked node).
      ... you might want to use this reverse dispatch method...

      I agree with the "hash-of-subrefs as dispatch table" recommendation, but the trinary operator is overkill here -- we don't have to worry about catching the "exists but false case" so a regular alternation would be enough:

      ( $hash{$funct} || \&some_default )->();
      The amazing thing about this place (the Monastery) is that by asking one question, I end up needing to ask many more because I'm always get pushed into new territory. All that to say, I'm not sure I understand Meryln's code as much I as I do yours, runrig. Both you and Zaxo talk about "dispatch tables" which I have read about here at PMs, but can't find in my well-worn Camel book. So, I'm going to Google and try to get my head around this, 'cuz it looks like I'm being pointed in that direction.

      —Brad
      "A little yeast leavens the whole dough."