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

I'm writing a CGI pgm in which I declare global variables at startup (main):
use strict; use CGI; use DBI; my ($COOKIE_ID, $COOKIE_NAME, $CGI, $DBH);
I then proceed to check/set cookies, via a subroutine that populates the $COOKIE_ID & $COOKIE_NAME variables. My problem is that once the subroutines are exited the values they set are lost. Any ideas? Would referencing the variables in the subroutines a/p the following help: $main::COOKIE_ID????

Edit by tye to add <code> tags

Replies are listed 'Best First'.
Re: Setting Global Variable in Sub
by stephen (Priest) on Apr 30, 2002 at 16:17 UTC

    First I'll tell you why global variables are Bad; then I'll explain how to get yourself into trouble if you so choose.

    Global variables are one of the most common sources of bugs in code. Worse, once they're in there, global variables are hard to stamp out, and the bugs they cause are difficult to trace. Here's an example:

    my $name = 'Jean ValJean'; my $number = 24601; steal_candlesticks(); become_mayor(); print "Who am I? $name, $number!\n"; sub become_mayor { if ($number == 1138) { do_thx(); } if ($number == 6) { do_prisoner(); } print "I am the mayor of this town\n"; } sub steal_candlesticks { if ($number = 1138) { do_thx(); } print "Took the candles, took my flight\n"; } sub do_thx { $name = 'THX'; } sub do_prisoner { undef $name; }

    The person that wrote this code expected the last line to print "Who am I? Jean ValJean, 24601!" Instead, we got "Who am I? THX, 1138!" And unfortunately, the bug is not easy to trace. Imagine if I had added flee_javair(), adopt_cosette(), etc., etc. The bug (which is in the if-statement for steal_candlesticks()) could be anywhere that $number is referenced... and there's no easy way to find it.

    You may be wondering why one would make so much fuss over a small program. The first reason is that useful programs tend to grow over time. Since we always hope that we're writing useful programs, we should always be prepared to wake up one morning to discover that our small CGI program has become gigantic. The second reason is that global variables become a hard habit to break once you're used to them. It's always best to start good habits as soon as possible.

    "So what do I do instead?" Glad you asked. The best way to get variables out of a subroutine is to return them from a subroutine. If you want to return two or three, you can simply return a list:

    sub who_am_i { my $name = 'Jean ValJean'; my $number = 24601; return ($name, $number); } # Chose different names here to emphasize that # these are different variables, but could # have chosen $name and $number again my ($prisoner_name, $prisoner_number) = who_am_i();

    Finally, here's the part where I tell you how to completely disregard all that I've been saying. (Several people have no doubt done so while I've been typing. :) )The my keyword is used to declare local variables. In other words, they're only visible in the subroutine (or other block) you declared them in.

    If you're using 5.6+, though, you can declare variables visible to the entire file using our. (If you're not, see use vars.) These are the globals you're looking for.

    Note: Code is not tested, and I still can't hit the high tenor notes in Les Miz.


    Update: Fixed typos, and for some reason I had the link to "our" labeled "my".

Re: Setting Global Variable in Sub
by mandog (Curate) on Apr 30, 2002 at 16:50 UTC
Re: Setting Global Variable in Sub
by derby (Abbot) on Apr 30, 2002 at 15:27 UTC
    Short answer - no. Long answer, you're declaring the variables with my which means they're lexical and will survive until the end of the block or until the end of file. ($main::xxx will let you access globals, not lexicals). Now you can have lexicals which act as globals (do a search on uninteded globals) ala

    #!/usr/bin/perl -w use strict; my( $COOKIE ) = "derby"; print $COOKIE, "\n"; asub(); print $COOKIE, "\n"; sub asub { $COOKIE = "derby_2"; }

    but I think in this case you may have another my( $COOKIE_ID ) in your sub which will mask the unintended global ala -

    #!/usr/bin/perl -w use strict; my( $COOKIE ) = ""; print $COOKIE, "\n"; asub(); print $COOKIE, "\n"; sub asub { my( $COOKIE ) = "derby_2"; }

    some code would be nice (and prepare yourself for the globals bad comments)


Re: Setting Global Variable in Sub
by Molt (Chaplain) on Apr 30, 2002 at 15:49 UTC

    Is there any chance you could post your code to this? It's a little difficult to tell what you mean without seeing it, to be honest.

    If you're scoping the variable with 'my' in order to get your script to work under 'use strict' then yes, $main::COOKIE_ID would help. 'My' stops the variable existing after the routine exits, but $main::COOKIE_ID is the global variable.

    A cleaner way would be to return the values to the main routine, so something like the following would happen... again tricky to show without knowing what this is being draped around, but here goes.

    use strict; ... # Call the subroutine to get the info. my ($cookieid, $cookiename) = getCookieInfo(); ... # The subroutine itself. sub getCookieInfo { # Keep these localised... my ($cookieid, $cookiename); ... # ... and then return them. return ($cookieid, $cookiename); }

    Still possibly cleaner ways would be to return hashes, with the keys of id, name etc and the suitable values. Slightly less transparent than the above though so I'll stick to just describing the above, but easy enough to get to here from there. This is especially useful when you're getting a lot of return values in my experience.

Re: Setting Global Variable in CGI scripts
by lestrrat (Deacon) on Apr 30, 2002 at 15:19 UTC

    A CGI is not persistent. So once the execution finishes, the stuff that the script used is gone, and there's no way you can retrieve it

    Besides, if you're not careful, it's a really bad idea to use the same global variable persisting across multiple requests. What if different users access the CGI script in succession? And I personally see no reason to keep a cookie out of all things...

    If you want to keep state, you would need to do it in some sort of db ( which, if the application is simple enough, can be just a plain text file ), or do some other trickery with hidden fields or something

Re: Setting Global Variable in Sub
by Rex(Wrecks) (Curate) on Apr 30, 2002 at 15:57 UTC
    Most monks are going to tell you that global vars are "Bad", and this is true in almost every case.

    There are answers above that tell you why, but neither of them really give an alternative.(Molt was answering at the same time as I) I would suggest returning the data from the function like:
    use strict; use CGI; use DBI; my ($COOKIE_ID, $COOKIE_NAME) = &Set_Cookie_Values(); my ($CGI, $DBH) ; sub SetCookieValues { my $id = "this is the cookie id" ; my $name = "this is the cookie name" ; return $id, $name ; }

    This way your vars are still scoped (although you still have a bunch in main but that is a different question :) and you are getting exactly what you need.

    "Nothing is sure but death and taxes" I say combine the two and its death to all taxes!