perlperl-module

How to use main:: data in modules?


In my script, I initialize several handlers and set variables which should be available for functions in separate modules. Which is the best way to use them ($q, $dbh, %conf) in modules?

Example pseudo module:

package My::Module

sub SomeFunction (
    @data = $dbh->selectrow_array("SELECT * FROM Foo WHERE Bar = ?", undef, $conf{Bar} );
    return $q->p( "@data" );
)
1;

Example pseudo script:

use CGI;
use DBI;
use My::Module;

our $q = new CGI;
our $dbh = some_connenction_function($dsn);
our %conf = ( Foo => 1, Bar => 2, Random => some_dynamic_data() );

I understand that using main:: namespace will work, but there should be cleaner way? Or not?


Solution

  • package My::Module Your modules should be independent of the context. That is, they shouldn't expect that $dbh is the database handler, or that they should return stuff in $q. or that configuration is kept in %conf.

    For example, what if you suddenly find yourself with two instances of database handles? What do you do? I hate it when a module requires me to use module specific variables for configuration because it means I can't use two different instances of that module.

    So you have two choices:

    Let's look at the first instance using your pseudo code:

    sub someFunction (
        %param = @_;
        %conf = %{param{conf};
    
        @data = $param{dbh}->selectrow_array(
           "SELECT * FROM Foo WHERE Bar = ?", undef, $conf{Bar} 
        );
        return $param{q}->p( "@data" );
    )
    1;
    

    Example pseudo script:

    use CGI;
    use DBI;
    use My::Module;
    
    my $q = new CGI;
    my $dbh = some_connenction_function($dsn);
    my %conf = ( Foo => 1, Bar => 2, Random => some_dynamic_data() );
    
    someFunction (
       q    => $q,
       dbh  => $dbh,
       conf => \%conf,
    );
    

    Here I'm using parameter calls. Not too bad. Is it? Now if you need another select statement, you can use different variables.

    Sure, but what if you don't want to keep passing variables all of the time. Well then, you can use an Object Oriented Techniques. Now, relax and calm down. There are many, many good reasons to use object oriented design:

    Let's see how an object oriented approach might work. First, let's see the main program:

    use CGI;
    use DBI;
    use My::Module;
    
    my $q = new CGI;
    my $dbh = some_connenction_function($dsn);
    my %conf = ( Foo => 1, Bar => 2, Random => some_dynamic_data() );
    
    my $mod_handle = My::Module->new (
       q    => $q,
       dbh  => $dbh,
       conf => \%conf,
    );
    
    $mod_handle->someFunction;
    

    In the above, I now create an object instance that contains these variables. And, magically, I've changed your Functions into Methods. A method is simply a function in your Class (aka module). The trick is that my instance (the variable $mod_handler has all of your required variables stored away nice and neat for you. The $mod_hander-> syntax merely passes this information for my into my functions I mean methods.

    So, what does your module now look like? Let's look at the first part where I have the Constructor which is simply the function that creates the storage for my variables I need:

    sub new {
       my $class = shift;
       my %param = @_;
    
       my $self = {};
       bless $self, $class
    
      $self->{Q} = $q;
      $self->{DBH} = $dbh;
      $self->{CONF} = $conf;
    
      return $self;
    }
    

    Let's look at the first thing that is a bit different: my $class = shift;. Where is this coming from? When I call a function with the syntax Foo->Bar, I am passing Foo as the first parameter in the function Bar. Thus, $class is equal to My::Module. It is the same as if I called your function this way:

    my $mod_handle = My::Module::new("My::Module", %params);
    

    instead of:

    my $mod_handle = My::Module->new(%params);
    

    The next thing is the my $self = {}; line. This is creating a reference to a hash. If you don't understand references, you should look at Mark's Reference Tutorial that's included in Perldocs. Basically, a reference is the memory location where data is stored. In this case, my hash doesn't have a name, all I have is a reference to the memory where it's stored called $self. In Perl, there's nothing special about the name new or $self, but they're standards that everyone pretty much follows.

    The bless command is taking my reference, $self, and declaring it a type My::Module. That way, Perl can track whether $mod_handle is the type of instance that has access to these functions.

    As you can see, the $self reference contains all the variables that my functions need. And, I conveniently pass this back to my main program where I store it in $mod_handle.

    Now, let's look at my Methods:

    sub SomeFunction {
        $self = shift;
    
        my $dbh = $self->{DBH};
        my $q = $self->{Q};
        my %conf = %{self->{CONF}};
    
        @data = $dbh->selectrow_array(
           "SELECT * FROM Foo WHERE Bar = ?", undef, $conf{Bar} 
        );
        return $param{q}->p( "@data" );
    }
    

    Again, that $self = shift; line. Remember I'm calling this as:

     $mod_handle->SomeFunction;
    

    Which is the same as calling it:

     My::Module::SomeFunction($mod_handle);
    

    Thus, the value of $self is the hash reference I stored in $mod_handle. And, that hash reference contains the three values I am always passing to this function.


    Conclusion

    You should never share variables between your main program and your module. Otherwise, you're stuck not only with the same name in your program each and every time, but you must be careful not to use your module in parallel in another part of your program.

    By using object oriented code, you can store variables you need in a single instance and pass them back and forth between functions in the same instance. Now, you can call the variables in your program whatever you want, and you can use your module in parallel instances. It improves your program and your programming skills.

    Beside, you might as well get use to object oriented programming because it isn't going away. It works too well. Entire languages are designed to be exclusively object oriented, and if you don't understand how it works, you'll never improve your skills.

    And, did I mention chicks dig it?

    Adium

    Before all the Perl hackers descend upon me. I want to mention that my Perl object oriented design is very bad. It's way better than what you wanted, but there is a serious flaw in it: I've exposed the design of my object to all the methods in my class. That means if I change the way I store my data, I'll have to go through my entire module to search and replace it.

    I did it this way to keep it simple, and make it a bit more obvious what I was doing. However, as any good object oriented programmer will tell you (and second rate hacks like me) is that you should use setter/getter functions to set your member values.

    A setter function is pretty simple. The template looks like this:

    sub My_Method {
       my $self =  shift;
       my $value = shift;
    
       # Something here to verify $value is valid
    
       if (defined $value) {
           $self->{VALUE} = $value;
       }
       return $self->{VALUE};
    }
    

    If I call $instance->My_Method("This is my value"); in my program, it will set $self->{VALUE} to This is my value. At the same time, it returns the value of $self->{VALUE}.

    Now, let's say I all it this way:

     my $value = $instance->My_Method;
    

    My parameter, $value is undefined, so I don't set the value $self->{VALUE}. However, I still return the value anyway.

    Thus, I can use that same method to set and get my value.

    Let's look at my Constructor (which is a fancy name for that new function):

    sub new {
       my $class = shift;
       my %param = @_;
    
       my $self = {};
       bless $self, $class
    
      $self->{Q} = $q;
      $self->{DBH} = $dbh;
      $self->{CONF} = $conf;
    
      return $self;
    }
    

    Instead of setting the $self->{} hash reference directly in this program, good design said I should have used getter/setter functions like this:

    sub new {
       my $class = shift;
       my %param = @_;
    
       my $self = {};
       bless $self, $class
    
      $self->Q = $q;       #This line changed
      $self->Dbh = $dbh;   #This line changed
      $self->Conf = $conf; #This line changed
    
      return $self;
    }
    

    Now, I'll have to define these three subroutines, Q, Dbh, and Conf, but now my SomeFunction method goes from this:

    sub SomeFunction {
        $self = shift;
    
        my $dbh = $self->{DBH};
        my $q = $self->{Q};
        my %conf = %{self->{CONF}};
    
        @data = $dbh->selectrow_array(
           "SELECT * FROM Foo WHERE Bar = ?", undef, $conf{Bar} 
        );
        return $param{q}->p( "@data" );
    }
    

    To this:

    sub SomeFunction {
        $self = shift;
    
        my $dbh = $self->Dbh;  #This line changed
        my $q = $self->Q;      #This line changed
        my %conf = %{self->Conf};   #This line changed
    
        @data = $dbh->selectrow_array(
           "SELECT * FROM Foo WHERE Bar = ?", undef, $conf{Bar} 
        );
        return $param{q}->p( "@data" );
    }
    

    The changes are subtle, but important. Now my new function and my SomeFunction have no idea how these parameters are stored. The only place that knows how they're stored is the getter/setter function itself. If I change the class data structure, I don't have to modify anything but the getter/setter functions themselves.

    Postscript

    Here's food for thought...

    If all of your SQL calls are in your My::Module function, why not simply initialize the $dbh, and the $q there in the first place. This way, you don't need to include the Use Dbi; module in your program itself. In fact, your program now remains blissfully ignorant exactly how the data is stored. You don't know if it's a SQL database, a Mongo database, or even some flat Berkeley styled db structure.

    I'm including a module I did for a job long, long ago where I tried to simplify the way we use our database. This module did several things:

    Take a look at it. It's not the greatest, but you'll see how I use object oriented design to remove all of the various issues you are having. To see the documentation, simply type in perldoc MFX:Cmdata on the command line.