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

Fellow Monasterians:

After Super Searching and reading a previous question, and bart's warning I still want to venture this question anyway (I think I have a legit use). But maybe there's another way.

The user clicks one of a dozen radio buttons in an HTML form, all with the name of "type" and values of 1-12 respectively. So, in my Perl:
use CGI; my $query = new CGI; my $t = $query->param('type');
So, if they clicked the 2nd button, $type = 2. For reasons too lengthy to discuss here, I want to have the equivalent of:
$type.$t = 1; #or $type2 = 1 or $type2 = true
I'm not really relying on the user's input to determine the variable name, as they are limited to a dozen radio buttons, all with set values. I just want to "build" a variable based on their choice. Otherwise, I see the only solution as:
if ($t == 1) { $type1 = 1; } elsif ($t == 2) { $type2 = 1; ... }
Seems like a bunch of code to do something that could be relatively quick (and seemingly safe). So, my questions: (1) how do I build this variable $type.$t and (2) is there a better way? Thanks in advance.

Update: davido's informative reply to the original question is worth a read by all. However, I was unable to make it work with HTML::Template (ideas are welcome, but I just need to keep the project moving) so instead adopted ishnid's solution without, I believe, jeopardizing security, at least in this case. Thanks all.

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

Replies are listed 'Best First'.
Re: Variable from a variable: the dark side?
by davido (Cardinal) on Apr 19, 2004 at 03:18 UTC
    I don't know what the lengthy reasons are... yours may be perfectly legit, but just had to wonder if perhaps you could use a hash instead like this:

    $type{$t} = 1;

    As far as building the soft reference, it's like this:

    $sref = $type . $t; ${$sref} = 1;

    So the code to implement a hash is actually a little more concise and more lexically friendly.

    Those are just a few thoughts. But if you really have a good reason for using softrefs, use them. On the other hand, if it's something that can be done with a hash instead, go with the flow and use the hash. ;) ...IMHO


    Dave

      Thanks davido, great response.

      Okay, "lengthy" reasons are this (I'm sorry I didn't do this before--after all, you ARE Perlmonks). I am using HTML::Template and validating user input from the form with is a .tmpl. If there are user errors, I'm using the associate feature to reprint the form, with values that were entered previously, and a list of errors. As far as I have been able to work it, the associate feature doesn't check previously checked buttons and boxes. So, I have to do it "manually." I'm using <tmpl_if type1>checked</tmpl_if>, etc. for all those 12 radio buttons. There is probably a more efficient way to do this and probably need to rethink this Rube Goldberg I have going.

      Again, thanks for your answer.

      Added moments later: I'm not use how the suggested hash method (davido, duff, bart) would work in this case, but I'm going to entertain it seriously.

      —Brad
      "A little yeast leavens the whole dough."
        The thing to remember is that the package global symbol table is pretty much a fancy special hash itself. In many cases, what can be done in global namespace, can be done in lexical namespace with a lexically scoped hash. Consider the following code:

        $t = 1; ${ "type" . $t } = "Hello world!\n"; print $type1; print ${$main::{type1}} __OUTPUT__ Hello world! Hello world!

        Ok, so what you've done here is use a "soft reference" to create an entry in the package global symbol hash. The first 'print' shows that the new entry functions just like a regular variable. The second 'print' shows that it is also just the scalar associated with the typeglob held in %main:: You have created, in other words, a hash element whos key is 'type1' in the global hash.

        Next, consider the following code:

        $t = 1; $hash{"type" . $t } = "Hello world!\n"; print $hash{type1}; print ${$main::{hash}}{type1}; __OUTPUT__ Hello world! Hello world!

        Now you've created a hash that has a key named 'type1' in a hash named %hash. There's nothing new about the first 'print'... and the second 'print' just dereferences the global symbol table to get at the element named 'hash', and further treats that element as a reference to a hash with a key named 'type1'. In so many cases, if you think you need something like my first example, you can probably get along just fine with something like my second example, without giving your code access to the symbol table.

        So using a hash instead of a symbolic ref, in many cases can be used instead of the symbolic ref in the first place. All you've really done anyway is create a hash entry either way.

        Now for the lexical part... you can further modify the second example as follows:

        my $t = 1; my %hash; $hash{"type" . $t } = "Hello world!\n"; print $hash{type1}; print ${$main::{hash}}{type1}; __OUTPUT__ Hello world!
        Now you've made your hash lexical, so it doesn't get an entry in the global symbol table, so the second 'print' doesn't print anything. What does this accomplish? Among other things, it reduces global namespace polution. It also reigns in the potential for all sorts of difficult to extinguish headaches, by allowing your code to run properly under use strict; (Without strictures, it's hard to detect misspelled variable names in code, for example.)

        Now for another reason of why this is desirable...

        In your case you're working with CGI. Though your particular code doesn't appear to be at risk of namespace contamination through malicious injections, once you start down the symbolic ref road, you really have to be careful. How easy it would be for you to inadvertantly create an entry in the global symbol table via a symbolic ref that happened to at some point originate via user input, or external file input. And when that happens, you have the potential for trouble.

        So again, feel free to play with matches if your intention and need is to have a flame. But if all you need is a little light, consider a lightbulb -- it's safer.

        Update: Thanks ysth for helping me with a couple edit suggestions that improved this node's readability and verbal accuracy.


        Dave

        Here's one way of doing it. I can't help but think that Template would make this so much easier. In your case, you may just want to use HTML::FillInForm. Check it out :)

        use strict; use CGI::Simple qw(-debug2); use HTML::Template; my $q = CGI::Simple->new(); my $t = HTML::Template->new( filehandle => \*DATA ); my $type = $q->param('type') || 1; $t->param( type_values => [ map { { value => $_, checked => ($_ eq $type ? 'CHECKED' : '') } } 1..12 ] ); print $q->header(), $t->output(); __DATA__ <TMPL_LOOP NAME="type_values"> <input type="radio" name="type" value="<TMPL_VAR NAME="value">" <TMPL_VAR NAME="checked"> /> </TMPL_LOOP>
        Perhaps you could use CGI.pm's "sticky forms" feature. If you get CGI.pm to create the entire radio button code, it will by default remember the previous value. You can still pub your radio buttons wherever you please in the HTML page. Here's an example to illustrate:
        #!/usr/bin/perl -w use strict; use CGI qw(:standard); use HTML::Template; my $template = new HTML::Template (filehandle => \*DATA); my @radio_btns = radio_group(-name=>'type', -value=>[ 1 .. 12 ]); $template->param(type.($_ + 1) => $radio_btns[$_]) for (0 .. $#radio_b +tns); print $template->output; __DATA__ <TMPL_VAR NAME="type1"> <TMPL_VAR NAME="type2"> <TMPL_VAR NAME="type3"> <TMPL_VAR NAME="type4"> <TMPL_VAR NAME="type5"> <TMPL_VAR NAME="type6"> <TMPL_VAR NAME="type7"> <TMPL_VAR NAME="type8"> <TMPL_VAR NAME="type9"> <TMPL_VAR NAME="type10"> <TMPL_VAR NAME="type11"> <TMPL_VAR NAME="type12">
Re: Variable from a variable: the dark side?
by Zaxo (Archbishop) on Apr 19, 2004 at 03:18 UTC

    You don't need to have your variables named that way. What's wrong with @type and $type[$query->param('type')]?

    To be honest, I think your design is likely to be amiss. See if you can't construct a more compact and coherent use of the form data.

    After Compline,
    Zaxo

Re: Variable from a variable: the dark side?
by exussum0 (Vicar) on Apr 19, 2004 at 03:15 UTC
    You have two options. From the perldoc for CGI...
    FETCHING THE VALUE OR VALUES OF A SINGLE NAMED PARAMETER:
    @values = $query->param('foo');
    -or-
    $value = $query->param('foo');
    
    Or you can have an array of names and do something like,
    foreach my $key ( @expectedVars ) { print $query->param( $key ); }
    It really depends on what you are going for. If you want a user to select some out of many, checkboxes or radio buttons with the same name is the way to go, with the first option. If you want a bunch of different values, you can do the second one, but you may be better off with straight forward like...
    if( $query->param( 'agreed' ) eq 'yes' ) { ... } if( $query->param( 'uppercase' ) eq 'sure' ) { ... }
    Play w/ it, see what you like.
Re: Variable from a variable: the dark side?
by duff (Parson) on Apr 19, 2004 at 03:22 UTC
    So, my questions: (1) how do I build this variable $type.$t and (2) is there a better way?

    The usual response when someone wants to do this is (and it applies equally here) use a hash.

    $t = $q->param('type'); $type{$t} = 1;

    That said, if you *really* want to create variables "the evil way", it's done like this:

    ${"type" . $type} = 1;

    If $type = 5, then this will create a variable called $type5 and give it a value of 1. Caveat lector!

Re: Variable from a variable: the dark side?
by thor (Priest) on Apr 19, 2004 at 03:40 UTC
    There are a couple of replies to this node that say "use a hash". Well, in Perl, there's a fancy data structure that acts just like a hash, but takes integer keys. It's faster than a normal hash, and takes less memory, too. "What magick is this?" you may ask. It's an array! I'm poking a bit of fun, but I think that hashes are a bit of overkill for something like this.

    thor