Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Avoiding user-input in sub calls.

by pekkhum (Sexton)
on Nov 01, 2003 at 12:43 UTC ( #303803=perlquestion: print w/ replies, xml ) Need Help??
pekkhum has asked for the wisdom of the Perl Monks concerning the following question:

Greetings, Perl Monks!
I have a program setup so that it chooses it's function(each one in a sub) based on user input. Naturally, it would be unwise to use this input directly in a call, so I devised a way to avoid it. My method works, but I think it obvious there is a better way to do it. Here is my current method, distilled:
#!perl -w use strict; use CGI qw/:Standard/; CGI::header(); my $sub = CGI::param('type'); my $data = CGI::param('info'); if($sub eq 'first'){ &one($data); } elsif($sub eq 'second') { &two($data); } else { &one($data); } sub one { print "Sub one says: $_[0]"; } sub two { print "Sub two says: $_[0]"; }

Is there a better way to get this done? Any help is greatly appreciated!

Comment on Avoiding user-input in sub calls.
Download Code
Re: Avoiding user-input in sub calls.
by bobn (Chaplain) on Nov 01, 2003 at 13:08 UTC

    Slightly better to use a hash of coderefs:

    %func = ( one => sub { print "Sub one says: $_[0]" }, two => sub { print "Sub two says: $_[0]" }, three => sub { print "Sub three says: $_[0]" }, four => sub { print "Sub four says: $_[0]" }, ); my $sub = CGI::param('type'); my $data = CGI::param('info'); if ( exists $func{$sub} ) { $func{$sub}->($data) } else { $func{one}->($data) }

    --Bob Niederman, http://bob-n.com

    All code given here is UNTESTED unless otherwise stated.

      Since when dealing with code refs, there is no difference between not existing and undefined, I always prefer to write:
      if ( exists $func{$sub} ) { $func{$sub}->($data) } else { $func{one}->($data) }
      as:
      ($func{$sub} || $func{one})->( $data );

      which at least ensures that the same parameters are passed to (and possibly returned from ) the default routine. More compact and fewer things to worry about from a maintenance point of view!

      Liz

Re: Avoiding user-input in sub calls.
by barrd (Parson) on Nov 01, 2003 at 13:14 UTC
    Hi pekkhum,
    There are indeed many ways of doing this, avoid using Switch.pm as that is too buggy.

    What I normally do in this situation is create an HTML drop down like so (which takes away 'true' user input like a text form):

    <select name="foo"> <option value="">Please make a selection <option value="first">First <option value="second">Second </select>
    Then in the script have a hash pointing to functions:
    my $doit = $q->param('foo'); my %functions = ('first' => \&one, 'second' => \&two );
    This can then be called by dereferencing the sub:
    &{ $functions{$doit} };
    Obviously it would probably be wise to have a default to act as an error trap but hopefully the above might give you some ideas.

    Another alternative would be to look at CGI::Application which is a handy tool for this kind of thing.

      This is the first mention I have heard of "dereferencing" a variable. I was looking for it in perldoc and found some references to it, but wish to double check what I have gathered. It seems to me:

      &{ $functions{$doit} };

      Would be seen by Perl as:

      "Take the value of $functions{$doit} and treat it as a sub name"

      Is that about right?
      I also keep seeing the use of my $thing = $q->param('whatever'); but am having trouble searching for it as I do not know what name it goes by...

      Thanks, for the help and better code.
        ..."dereferencing" a variable...
        It isn't dereferencing a 'variable' but a subroutine.
        "Take the value of $functions{$doit} and treat it as a sub name" Is that about right?
        More or less, yeah. But I have to admit that my powers of syntax explaining are pretty poor, I understand in my head what is going on but I don't want to lead you down the garden path with my crap explanations. There are many people here who could explain far more eloquently what is going on and hopefully one of them will step in to help both of us ;)

        I also keep seeing the use of my $thing = $q->param('whatever'); but am having trouble searching for it as I do not know what name it goes by...

        This is the OO ('Object Oriented') interface to CGI.pm:

        use CGI; my $q = CGI->new; my $value = $q->param('name');
        Roughly equivalent to:
        use CGI; my $value = CGI::param('name');
        except the OO way rends to be more flexible and earsier to modify (eg, you could write/use a module that inherits from CGI, by modifying only the
        use
        and
        new
        statements. There's other reasons, but it's too early.

        --Bob Niederman, http://bob-n.com

        All code given here is UNTESTED unless otherwise stated.

        &{ $functions{$doit} };

        Would be seen by Perl as:

        "Take the value of $functions{$doit} and treat it as a sub name"

        No, there's some confusion here about references. perlman:perlref is the definitive reference (no pun intended), but I'll try to give you the short answer.

        The syntax &{ SOMETHING } is, as you correctly said, a dereference. It says "Treat SOMETHING as a code reference", which means that the subroutine referred to by SOMETHING is executed. That means that SOMETHING is taken to be a reference, and in the example given, that's just what it is, as is evidenced by the code:

        my %functions = ('first' => \&one, 'second' => \&two );

        That \&one syntax is creating a hard reference to the subroutine one().

        If, however that SOMETHING is not actually a hard reference, but is just a plain old scalar, then the "value of the scalar is taken to be the name of a variable, rather than a direct link to a (possibly) anonymous value." (from perlman:perlfref). So if the code example would have looked like this:

        my %functions = ('first' => 'one', 'second' => 'two' );

        ... then you would be using a symbolic reference, which is what you mean by "Take the value of $functions{$doit} and treat it as a sub name". NOTE that this whole Symbolic reference business is disallowed by use strict 'refs', meaning that our friend SOMETHING must be an actual reference, and not just a name. This is probably a Good Thing.

        Hope this helps. And by the way, I think the syntax $coderef->() is preferable to the equivalent &{$coderef}, since I think it just looks clearer that you're calling a subroutine...

        --
        3dan

       Using a HTML form with a drop down doesn't take away the user input; it's still not trusted.

       Any value may be entered by the user capable of saving your source somewhere and editing it; or facing the whole thing with LWP, etc.

       A minor point I know, but this came up at work fairly recently. All text fields were validated at submission time, but drop downs were for some bizarre reason taken as "trusted", and their values were injected directly into SQL. (Something else that's changed now).

      Steve
      ---
      steve.org.uk
Re: Avoiding user-input in sub calls.
by Roger (Parson) on Nov 01, 2003 at 13:31 UTC
    What you are trying to write is like a message despatcher, and Yes there are many techniques -

    Method 1
    if (condition1) { code_for_condition1($data) } elsif (condition2) { code_for_condition2($data) } else { default_condition($data) }
    The first method is the obvious method, it is not necessarily a bad method at all.

    Method 2
    %despatcher = ( ident_1 => \*code_for_condition1, ident_2 => \*code_for_condition2, ... }; &$despatcher{$identifier}($data); sub code_for_condition1 { ... } sub code_for_condition2 { ... }
    The second method using a hash table requires more Perl knowledge. It has a slightly more complicated syntax structure, and a hash table overhead. It is nevertheless a good method.

    <TO BE CONTINUED...>Too late for bed time tonight, I will add some more tomorrow...

Re: Avoiding user-input in sub calls.
by runrig (Abbot) on Nov 01, 2003 at 16:49 UTC
    Yet another way (OO style):
    package Foo; sub one { ... } sub two { ... } sub AUTOLOAD { goto &one; } package main; # Then in your main routine Foo->$sub($data);
    Doing it this way you'd have to first make sure that $sub is not one of the UNIVERSAL::* methods (can, isa, ??? -- update: or define your own 'can' and 'isa' which just goes to 'one').
Re: Avoiding user-input in sub calls.
by clscott (Friar) on Nov 01, 2003 at 19:46 UTC

    You may be interested in CGI::Application as it looks like it fits in to how you're trying to structure you application.

    --
    Clayton
Re: Avoiding user-input in sub calls.
by pizza_milkshake (Monk) on Nov 02, 2003 at 00:11 UTC
    perl -wle'%subs = ("a"=>sub{"A"}, "b"=>sub{"B"}, "c"=>sub{"C"}); print + &{$subs{((keys %subs)[rand keys %subs])}} for 1..10'

    <code> map print(chr(hex((q{6f634070617a6d692e7273650a}=~/../g)hex))),(q{375542349abb99098106c}=~/./g)<code>

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://303803]
Approved by BazB
Front-paged by davido
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others chanting in the Monastery: (10)
As of 2014-09-17 08:11 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (66 votes), past polls