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

Yesterday I develop solution (may be not first) to well known problem with cgi scripts if/else selector:
$actions = $CGI->param ('action'); $to_do = new INVOICES(); if ($actions eq 'delayedinv') { $to_do->delayedinv() } elsif ($actions eq 'del_inv') { $to_do->del_inv() } elsif ($actions eq 'payed' ) { $to_do->payed () } elsif ($actions eq 'stats' ) { $to_do->stats () } ... skip ... else { $to_do->defscreen()}
We all know solution for this from Perl Cookbook with a hashtable.

so my solution is ...
$actions = $CGI->param ('action'); $to_do = new INVOICES(); eval {$to_do->$actions} || defscreen();
also we can implement with regexp a filter to prevent access to private methods.

Any emotions, opinions, points of view?

Edit kudra, 2002-09-06 Replaced br tags with code tags

Replies are listed 'Best First'.
Re: input switch
by broquaint (Abbot) on Sep 06, 2002 at 12:28 UTC
    Instead of the eval() you could check if the method exists and then run it
    if(ref $to_do and UNIVERSAL::can($to_do, $actions)) { $to_do->$actions(); } else { defscreen(); }
    This also avoids the sticky situation when a method dies within the eval() and the script goes on. Also if you want to have private methods then you might want to look at Attribute::Protected.
    HTH

    _________
    broquaint

      As I explore in Programing Perl there is no need in eval at all. We can simply write $to_do->$actions();, and Perl makes all job for us.
Re: input switch
by LTjake (Prior) on Sep 06, 2002 at 12:46 UTC
    You might also look into using CGI::Application. It uses "run modes" to sort out these issues.

    Simplified Example (modified from CGI::App docs):
    # In "WebApp.pm"... package WebApp; use base 'CGI::Application'; sub setup { my $self = shift; $self->mode_param('action'); $self->run_modes( 'delayedinv' => 'delayedinv', 'del_inv' => 'del_inv', 'played' => 'played' 'stats' => 'stats' 'AUTOLOAD' => 'defscreen' ); } sub delayedinv { ... } sub del_inv { ... } sub payed { ... } sub stats { ... } sub defscreen { ... } 1; ### In "webapp.cgi"... use WebApp; my $webapp = WebApp->new(); $webapp->run();
    It has many other nice features. See the docs for more info.
Re: input switch
by Aristotle (Chancellor) on Sep 06, 2002 at 14:22 UTC
    As has already been mentioned, using eval at that point is not a good idea, and you should also make sure that only allowed methods can be called. Personally, when I used a similar setup, I simply used a name prefix for all "public" methods.
    my $action = "do_" . $CGI->param('action'); my $to_do = new INVOICES; $to_do->can($action) ? $to_do->$action() : defscreen();

    Now all your action methods must be called do_foo (called by action=foo), do_bar etc, but this makes sure that someone can't call f.ex the method delete_file by putting action=delete_file in the URL. The other way would be to have a method, analogous to can, which checks if $action is an allowed method name, but I don't prefer that solution because it requires keeping several locations in the code synchronized.

    (Btw, I see no mys in your code; you are of course using strict and warnings, I hope?)

    Makeshifts last the longest.

Re: input switch
by RatArsed (Monk) on Sep 06, 2002 at 12:04 UTC
    Just make sure that you have a regexp to check that only valid methods are called. (for security reasons)

    Net result: If(){}elsif(){}... route is going to be safer, and probably "as fast" (string manipulation and checking is always hideous).

    --
    RatArsed

      But there is no string manipulation nor checking in his second solution; whereas the first you are advocating does have string checking. String manipulation isn't exactly hideous either; a lot work has been put into making Perl blazing fast for that kind of task. (Number crunching on the other hand is hideous indeed.)

      In other efficiency notes, remember that even though the name of the method in $obj->method() is known at compile time, Perl still has to look it up at run time. This is exactly the same amount of work as $obj->$anymethod() takes, so the if elsif route has to do as much work as the simpler method in addition to the string comparisons.

      Note also that the best optimization method for Perl scripts is to try to offload as much work as possible into builtins. The if elsif does a lot of work in the script itself.

      Lastly, I'd argue against the if elsif route simply because it makes for far harder to maintain code. The data driven method frees programmer time I can spend on getting more code written and/or bugfixed.

      Makeshifts last the longest.

        Right, the two main approaches are roughly:

        if...elsif... which requires string comparison before deciding which method it'll execute. (and of course, the string comparisons can have their order optimised)

        eval()ing, which needs to be wrapped up with a security check to ensure that someone doesn't try to do any "bad things" with it. Which means a series of string compares, to ensure it's a valid choice. Thus effectively becoming roughly the same thing...

        I'd argue this latter option is more difficult to maintain, as it could be argued that it'd be easier to miss out of the regexp.

        --
        RatArsed

Re: input switch
by larryk (Friar) on Sep 06, 2002 at 14:30 UTC
    You could use Switch:
    use Switch; switch ($CGI->param('action')) { case 'delayed_inv' { $to_do->delayedinv() } case 'del_inv' { $to_do->del_inv() } case 'payed' { $to_do->payed() } ... else { $to_do->defscreen() } }
       larryk                                          
    perl -le "s,,reverse killer,e,y,rifle,lycra,,print"
    
      The problem with that module is that it's a source filter and easily malfunctions. For any than the simplest scripts, it won't work.

      Makeshifts last the longest.