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

Let's say I'm using some module, Foo (which I did not write). At the top of the module, we see stuff like the following:

package Foo; $Foo::VERSION='3.02'; sub set_globals { $DEBUGGING = 0; $LOG_ERROR = 0; $ERR_MIN = 20; $DEFAULT_XFR = 'G0023'; } set_globals(); #... more code ...

This is actually an OO module that I need to use as a base class, but I want to make sure that I safely override those globals with my own values.

################################ package Bar; ################################ $VERSION = 1.0; use strict; use Foo; use vars qw/ @ISA /; @ISA = qw/ Foo /; INIT { $Foo::LOG_ERROR = 1; $Foo::ERR_MIN = 20; $Foo::DEFAULT_XFR = 'H00293'; } sub new { my ( $class, $args ) = @_; # etc...

Naturally, in my own code, I have:

use strict; use Foo::Bar; my $obj = Foo::Bar->new;

What I was wondering is whether or not I could alter the contents of those globals at the time that I use Foo::Bar. For instance, perhaps I want to set $Foo::LOG_ERROR to 0, I might want a statement like this:

use Foo::Bar qw/noLog/;

The above would ideally be able to change several globles to match whatever configuration is needed at run-time. To compound things, the base class, Foo, has subs that can be called as OO methods or functions, so I can't try any trickery in the new constructor as I cannot guarantee that it will be called.

I know this sounds a bit strange, but the author of Foo has requested that my subclass have a way to set those globals at load time. Any ideas welcome.

Cheers,
Ovid

Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Replies are listed 'Best First'.
Re: Setting Globals in a Base Class
by dragonchild (Archbishop) on Nov 07, 2001 at 00:58 UTC
    Write your own import() function. That will catch allt he stuff to the right of use Foo::Bar. So, something like (untested!):
    package Foo::Bar; sub import { for (@_) { $Foo::Logging = 0 && next if $_ eq 'noLog'; $Foo::MyThing = 1 && next if $_ eq 'myThing'; } }

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      I like that. The only problem that I see is that the base class also has functions that it exports and, under certain circumstances, will AUTOLOAD functions that don't exist, but which the client requests be exported. To get around that, I think I would need to trap those functions that I need, set the globals, and remove those functions from the import list. What do you think about the following (also untested :) code?

      package Foo::Bar; sub import { my %handlers = ( DEFAULT_XFR => \&set_xfr, NO_LOG => \&disable_logging ); my @args; for my $i ( 1 .. $#_ ) { push(@args, [ $_[$i], $i ]), next if $_[$i] eq 'DEFAULT_XFR'; push(@args, [ $_[$i], $i ]), next if $_[$i] eq 'NO_LOG'; } # two loops because we don't want to splice @_ while looping over +it foreach my $arg ( @args ) { if ( exists $handlers( $arg->[0] ) ) { $handlers( $arg->[0] )->( $arg->[1], @_ ); } } Foo::Bar->export_to_level( 1, @_ ); } sub set_xfr { my $index = shift; if ( defined $_[$index + 1] and $_[$index + 1] =~ /^([A-Z]\d{4,5} +)$/ ) { $Foo::DEFAULT_XFR = $1; splice @_, $index, 2; } else { $Foo::DEFAULT_XFR = 'H00293'; splice @_, $index, 1; } } sub disable_logging { # same concept; }

      Cheers,
      Ovid

      Update: I'd also need to set a flag to see if the module was required instead of used. A require won't call the import() method, thus necessitating that the INIT() function still remain as a fall-back (and it would only fire if the import wasn't called).

      Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

        I have found the export_to_level function in Exporter to be unmaintained and unreliable. Its error handling in particular leaves something to be desired due to poor maintainance and bad misunderstandings about the internals of Carp that it is messing around with.

        I would therefore recommend using:

        goto &Exporter::import;
        rather than using export_to_level. Yeah, it is a hack. But it is a hack that works reliably.
Re: Setting Globals in a Base Class
by perrin (Chancellor) on Nov 07, 2001 at 01:50 UTC
    It seems like the correct solution would be for Foo to provide setter methods for these variables, which your class can call in a constructor. Otherwise, you have to know far too much about the implementation of Foo.

      That would be a lot cleaner, but there are no such methods and any code that I produce can't rely on them, due to backwards-compatibility requirements. Sigh... Even if those methods werre available, my constructor may never get called as the the methods can be used as functions.

      I'm seeing some issues with dragonchild's solution, but it certainly seems like the only workable prospect (and it's pretty cool, too).

      Cheers,
      Ovid

      Update: Hmm... or I could create the mutators myself and add them to Foo's symbol table.

      Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

        Ick. I don't mean this as an insult (I'm sure it was nothing but a wild idea), but that's a pretty Microsoftian way of thinking, with Foo offering a modified interface when Bar is loaded (for some reason it makes me think of the MSOffice installers upgrading half your OS behind your back). Also you gain nothing because to modify Foo, Bar still has to know far too much about it.

        So I suggest you rather not go there :)

Re: Setting Globals in a Base Class
by alien_life_form (Pilgrim) on Nov 07, 2001 at 15:14 UTC
    Greetings,

    Things popping up from my thought pool:

    • Do your glob setting in a BEGIN block (that'll settle the load time requirement, methinks).
    • If import is tied up you can hook the lesser known export_fail. For instance, from Carp.pm:
      @EXPORT_FAIL = qw(verbose); # hook to enable verbose mode # if the caller specifies verbose usage #("perl -MCarp=verbose script.pl") # then the following method will be called # by the Exporter which knows # to do this thanks to @EXPORT_FAIL, above. #$_[1] will contain the word # 'verbose'. sub export_fail { shift; $Verbose = shift if $_[0] eq 'verbose'; return @_; }
    • I would also override the set_globals method - but that may be voided by other specs, I dunno.
    • The author of the base class should 'use strict' ;)

    Cheers,
    alf
    You can't have everything: where would you put it?