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

Background
I'm developing a server framework (based on Net::Server). I want the framework extensible enuff so it will load modules at runtime dynamically, ie, there are no config files anywhere explicitly stating what modules are where.

Problem
I'm doing a search with file::find in for any file within a specified path. I can find the modules i'm after and require them, however when i go to access the new method I'm finding out:

Can't locate object method "new" via package "api::bess::query::Query" (perhaps you forgot to load "api::bess::query::Query"?)

To stave off the obvious, yes there is a new method in the Object being loaded, and I know its loaded as I created a BEGIN block that printed to a logfile. I'm now wondering what might be causing the problem and how i can fix it.

Snippet below:

sub load_api_modules{ my $self = shift; my @superclass = split(/\//, $File::Find::dir); my @class = split(/./, $_); if ($File::Find::name =~ /\.pm$/) { require "$_"; my $name = $File::Find::name; $name =~ s/\//\:\:/g; # Convert slashes (/) to :: substr($name, -3,3, ''); # Remove the .pm at the end $self->{_$superclass[1].$superclass[2]} = new $name; print STDERR $File::Find::name."\n"; } }

Replies are listed 'Best First'.
Re: Dynamically load unknown modules
by demerphq (Chancellor) on Aug 27, 2002 at 14:51 UTC
    First off, personally I stay as far away from indirect object syntax as I can. It _will_ bite you at some point or another. Replace that new $name to $name->new() and teach yourself never to use the syntax and youll never get bitten.

    As for the solution to your problem it seems like it would be pretty likely that its due to filename case issues on your system. The file name is probably: api/bess/query/Query.pm but the package name is "Api::Bess::Query" so when you require it you cant directly use the filespec to determine the actual package contained. This is more serious than you may think as the rules do NOT say that the contents of a file that is use()d/require()d is the same as the file is named. Thus there is no reliable way to go from the filename to the package name that it contains. For instance

    #file: Bar.pm packge Foo; sub yada { print "...\n" }; __END__ perl -MBar -e "Foo::yada()"
    should work just fine.

    Thus you will need to parse the file looking for package statements. This gets even more complex in that a file can contain multiple packages (I do this very often indeed) and also could be dynamically creating packages at run or compile time. Thus the file when required may actually generate a class and not contain anything parseable.

    So parse the file for package statements and then act apon them, but expect that it wont be a generalized solution.

    UPDATE: I suppose what you could do is find out the packages that exist prior to the require as well as after, filter the ones that already existed and then go from there. I know its possible but offhand im not sure how to do it. Id have to research into it more extensively than I have time. Perhaps a guru (there are more :-) is around that can clarify the approach.

    Yves / DeMerphq
    ---
    Software Engineering is Programming when you can't. -- E. W. Dijkstra (RIP)

Re: Dynamically load unknown modules
by fruiture (Curate) on Aug 27, 2002 at 14:31 UTC

    It's generally not a Good Thing to access globals from outside a sub if these values should much better be passed as parameters. The unqualified use of '$File::Fimnd::name','$File::Find::dir' and '$_' makes it quite impossible to look at this function alone.

    Second: it's not neccessary to quote "$vars", this is Perl, not Bash.

    Third: 's/\//\:\:/g' can much better be written as 's#/#::#g'.

    #fourth: #self->{_$superclass[1].$superclass[2]} = new $name; #that's not what you want, you want: $self->{ "_$superclass[1].$superclass[2]" } = ...;

    Fifth, and most important: what does $name contain at the 'new $name' point? Is there really a package defined in that dynamic module? Did you try "$name->new"?

    --
    http://fruiture.de

      Those "unqualified variables" are part of the standard interface of File::Find. It's shipped with Perl for several years. The interface does suck. (I think you accidentally left the concatenation operator in your fourth point, too.)

        Yes, and because the interface sucks (imho,too), i don't want to have to remember it when reading a subroutine that actually has not much to do with it.

        Hmm, i assume the '.' is wanted inside the string, because the leading '_' without concatenation operator implies the author thought "in hash keys it's like in double-quoted strings". I think he'll figure out himself what he might have meant ;)

        --
        http://fruiture.de
Re: Dynamically load unknown modules
by Ryszard (Priest) on Aug 27, 2002 at 15:01 UTC
    Ok, after hacking about a bit I've found this snippet does what i want.

    Code review invited (yes i'm going to change the name of "superclass").. :-)

    sub post_configure_hook { my $self = shift; $self->logObj( Logger->new(name => 'log/gaa') ); $self->xml( new XML::Simple(xmldecl => 1,forcearray => 1) ); # Go find all the modules find ({no_chdir => 1, wanted => sub { $self->load_api_modules($Fil +e::Find::name) }, follow =>1}, 'api'); } sub load_api_modules{ my $self = shift; my $filename = shift; if ($filename =~ /\.pm$/) { my @superclass = split(/\//, $filename); require "$filename"; my $name = $filename; $name =~ s#/#::#g; # Convert slashes (/) to :: substr($name, -3,3, ''); # Remove the .pm at the end $self->{$superclass[1].$superclass[2]} = new {split(/\./, $sup +erclass[$#superclass])}[0]; $self->logObj->log(location => 'Initialisation', message => 'l +oaded: '.$File::Find::name); } }

      general: long but full lines are always hard to read.

      #example: your call of find() find ( { no_chdir => 1, wanted => sub { $self->load_api_modules($File::Find::name) }, follow =>1 }, 'api');

      Allow me to rewrite them load_api_modules() method:

      sub load_api_modules { my $self = shift; my $file = shift; local $_ = $file;#will work with $_ and keep $file return unless s/\.pm$//; require $file; my @sup = split m#/#; # s#/#::#g; unneccessary $self->{ $sup[1].$sup[2] } = $sup[@sup-1]->new(); $self->logObj->log( location => "Initialisation", message => "loaded: $file" ); }

      HTH

      --
      http://fruiture.de
        cool, thx for a fresh viewpoint.