Search code examples
perloopperl-module

Setting Up Perl Module Structure


I'm having trouble figuring out how to structure Perl modules in an object oriented way so I can have one parent module with a number of submodules and only the specific submodules that are needed would be loaded by a calling script. For example I want to be able to make method calls like so:

use Example::API;    
my $api = Example::API->new();

my $user = {};
$user->{'id'} = '12345';

$api->Authenticate();
$user->{'info'} = $api->Users->Get($user->{'id'});
$user->{'friends'} = $api->Friends->Get($user->{'id'});

In terms of file structure I'd like to have the modules setup as follows or in whatever structure is required to make everything work correctly:

api.pm
users.pm
friends.pm
...

The reason I want to do this in the first place is so that if someone just wants to authenticate against the API they don't have to load all the other modules. Similarly, if someone just wants to get a user's information, they wouldn't have to load the friends.pm module, just the users.pm. I'd appreciate it if you could provide the necessary example Perl code for setting up each module as well as explain how the file structure should be setup. If I'm going about this all wrong to accomplish what I'm try to accomplish I'd appreciate an explanation of the best way to do this and some example code on how it should be setup.


Solution

  • From your example, in your main module I assume you will be providing accessor methods to get at the subclasses. So all you have to do is include require Sub::Module; at the top of that method. Nothing will happen at compile time, but the first time that code is run, perl will load the module. After the first load, the line require Sub::Module; will become a no-op.

    If all of your code is object oriented, you won't need to worry about importing functions. But if you do, the statement use Module qw(a b c); is interpreted as:

    BEGIN {
        require Module;
        Module->import(qw(a b c));
    }
    

    BEGIN makes it happen at compile time, but there is nothing stopping you from using the internals at run time. Any subroutines you import at runtime must be called with parenthesis, and prototypes will not work, so unless you know what you are doing, runtime imports are probably a bad idea. Runtime requires and access via package methods are completely safe though.

    So your $api->Users method might work something like this:

    # in package 'Example::API' in the file 'Example/API.pm'
    
    sub Users {
        require Example::API::Users;  # loads the file 'Example/API/Users.pm'
        return  Example::API::Users->new( @_ ); # or any other arguments
    }
    

    In my examples above, I showed two translations between package names and the files they were in. In general, all :: are changed to / and .pm is added to the end. Then perl will search for that file in all of the directories in the global variable @INC. You can look at the documentation for require for all of the details.

    Update:

    One way to cache this method would be to replace it at runtime with a function that simply returns the value:

    sub Users {
        require Example::API::Users;
        my $users = Example::API::Users->new;
    
        no warnings 'redefine';
        *Users = sub {$users};
    
        $users
    }