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

Hey folks:
I have some dispatch logic, which builds the components on a page by pushing values into four arrays: @incl, @excl, @msg, @err, like so:

push(@incl,"&CreateLogInForm(\$page,\$error)");
At the end of it all, is some logic which sorts out the includes and excludes to construct a web page. My question is this: How can I execute an array element?

If $incl[n] = "CreateLogInForm($page,$error)", how can I have it run the subroutine, passing the appropriate arguments so I can concatenate its results to the cgi page I'm building?

Here is what is not working:

foreach $incl (@incl) { $page .= &$incl; }
This throws an error to the browser: "Undefined subroutine &main::CreateLogInForm($page,$error) called . . . " The same error is reported in /var/log/apache-ssl/error.log.

Can anyone help me past this one, please?
-- Hugh

Replies are listed 'Best First'.
Re: How do I execute an array element?
by rhesa (Vicar) on Feb 02, 2006 at 02:27 UTC
    You shouldn't stringify your "logic" elements. It will work better if you store the actual references to the sub and its arguments:
    push @incl, [ \&CreateLoginForm, \$page, \$error ]; # later on: foreach my $incl ( @incl ) { my ($sub, @args) = @$incl; $page .= $sub->(@args); }
    Depending on the logic of the rest of your code, you may be better off storing the actual arguments instead of references to them:
    push @incl, [ \&CreateLoginForm, $page, $error ];
    But I can't determine that from what you've told us. It might avoid surprises though.

    Question: is the $page you push into @incl the same as the $page you use in the foreach loop?

Re: How do I execute an array element?
by ikegami (Patriarch) on Feb 02, 2006 at 04:20 UTC

    $incl has to contain a reference to a function (e.g. $incl = \&my_func), or the name of a function if strict 'subs' is off (e.g. $incl = 'my_func'). In your case, it contains much more than that. It contains a bit of (uncompiled) Perl code.

    You have two options.

    • You can use eval EXPR to execute the Perl code. That's rather slow, and it can be vulnerable to injection attacks.

      push(@incl,"&CreateLogInForm(\$page,\$error)"); foreach $incl (@incl) { $page .= eval $incl; }
    • You can wrap the function call (your Perl code) with an argument-less subroutine. This method doesn't suffer from the problem listed above.

      push(@incl, sub { &CreateLogInForm($page,$error) }); foreach $incl (@incl) { $page .= &$incl; }

    There are subtle differences in the meaning of $page and $error between the two:

    • { my $page = 'abc'; my $error = 'def'; push(@incl,"&CreateLogInForm(\$page,\$error)"); $page = 'ghi'; $error = 'jkl'; } foreach $incl (@incl) { my $page = 'mno'; my $error = 'pqr'; $page .= eval $incl; # Uses 'mno' and 'pqr' }
    • { my $page = 'abc'; my $error = 'def'; push(@incl, sub { &CreateLogInForm($page,$error) }); $page = 'ghi'; $error = 'jkl'; } foreach $incl (@incl) { my $page = 'mno'; my $error = 'pqr'; $page .= &$incl; # Uses 'ghi' and 'jkl' }
Re: How do I execute an array element?
by jjohn (Beadle) on Feb 02, 2006 at 21:25 UTC

    The dispatch metaphor I use is something like the following:

    $Dispatch = ("foo" => \&foo_func, "bar" => \&bar_func,); $action = $q->get_this_action(); if (exists $Dispatch->{$action}) { $Dispatch->{$action}->(@args_that_everyone_gets); } else { # no action, do the default, if any }

    Of course, you'll have to figure out what the current action should be and how args determined for the dispatch functions.

    This bit of code is nearly as useful as an echo loop. I agree with the earlier poster that stringifying your code is probably not I good idea. eval() is powerful ju-ju and can easily be abused by black hats if you're aren't careful (e.g. something like this type of bug afflected SOAP::Lite awhile ago).

    By explicitly mapping your "public" function names to private implementing functions, you can protect yourself from someone trying to execute an arbitrary perl function.

    Hope this helps,

    --Joe