Require Perl Modules using an Aliased Name
Is there a different way to alias Foo::Bar::Baz:: as Things:: such that I can require Things::ThingA?
Yes. There are two requirements for this to work:
Create the package alias as you have already done.
BEGIN { *Things:: = *Foo::Bar::Baz:: }
Create a symbolic link to
mylibs/Things
from yourmylibs/Foo/Bar/Baz
directory (wheremylibs
is the path to your Perl modules)(Make a link from the
Foo/Bar/Baz.pm
file toThings.pm
, too, if you want)
Once you have done this, and call eval "require Things::Quux"
or eval "use Things::Quux"
, Perl will load the file in mylibs/Things/Quux.pm
, which is the same as the mylibs/Foo/Bar/Baz/Quux.pm
file. That file has a package Foo::Bar::Baz::Quux
statement in it, but since that package is already aliased to the Things::Quux
namespace, all of its subroutines and package variables will be accessible in either namespace.
Is there some other generally accepted method of tying together packages at different levels of the same namespace to obviate the need for all this?
It's not clear what your object model is, but if *::Thing1
, *::Thing2
, etc. are all implementations of some common base class, you could consider a factory method in the base class.
package Foo::Bar::Baz;
sub newThing {
my ($class, $implementation, @options) = @_;
eval "use $class\::$implementation; 1"
or die "No $implementation subclass yet";
no strict 'refs';
my $obj = "$class\::$implementation"->new(@options);
return $obj;
}
Now Foo::Bar::Baz::Thing7
(which may or may not be aliased to Things::Thing7
) will only be loaded if it is needed, say, from a call like
my $obj7 = Foo::Bar::Baz->newThing("Thing7",foo => 42);
print ref($obj7); # probably Foo::Bar::Baz::Thing7
How black do you like your magic?
We all know that, in order to require
modules, Perl looks through @INC
to find the file it wants to load. One of the little-known (and even-less-used) aspects of this process is that @INC
isn't limited to only contain filesystem paths. You can also put coderefs there, allowing you to hijack the module loading process and bend it to your will.
For the use case you've described, something like the following (untested) should do the trick:
BEGIN { unshift @INC, \&require_things }
sub require_things {
my (undef, $filename) = @_;
# Don't go into an infinite loop when you hit a non-Thing:: module!
return unless $filename =~ /^Thing::/;
$filename =~ s/^Thing::/Foo::Bar::Baz::/;
require $filename;
}
Basically what this does is, as the first entry in @INC
, it looks at the name of the requested module and, if it starts with Thing::
, it loads the corresponding Foo::Bar::Baz::
module instead. Simple and effective, but really easy to confuse future maintenance programmers (including yourself!) with, so use with caution.
As an alternate approach, you also have the option of specifying a package name in the module which doesn't correspond to the physical path of the file - the two are normally the same by convention, to make life easier when reading and maintaining the code, but there's no technical requirement for them to match. If the file ./lib/Foo/Bar/Baz/Xyzzy.pm
contains
package Thing::Xyzzy;
sub frob { ... };
then you would use it by doing
require Foo::Bar::Baz::Xyzzy;
Thing::Xyzzy::frob();
and Perl will be perfectly happy with that (even though your coworkers may not be).
Finally, if you want to get rid of ALL_THINGS
, take a look at Module::Pluggable. You give it a namespace, then it finds all available modules in that namespace and gives you a list of them. It can also be set to require
each module as it is found:
use Module::Pluggable require => 1, search_path => ['Foo::Bar::Baz'];
my @plugins = plugins;
@plugins
now contains a list of all Foo::Bar::Baz::*
modules, and those modules have already been loaded with require
. Or you can just call plugins
without assigning the result to a variable if you only care about loading the modules and don't need a list of them.
Messing with typeglobs is like nuclear overkill here. Module::Runtime is the standard way to load modules at runtime based on configuration data. At this point everything can be ordinary variables. There is no benefit in using a constant here.
Here is my suggestion as from our IRC chat.
package Foo::Bar::Baz;
use strict;
use Module::Runtime "require_module";
use List::Util "uniq";
my $prefix = "Things::LetterThings";
my %prop_module_map = (
foo => [ qw{ThingC ThingF ThingY} ],
bar => [ qw{ThingL ThingB} ],
baz => [ qw{ThingA ThingB ThingC ThingE ThingG ThingH ThingZ} ],
# or
# ALL => [ qw{ThingA .. ThingZ} ],
);
my @all_modules = uniq map { @$_ } values %prop_module_map;
sub load_modules {
my $self = shift;
# map module list if matching property found, otherwise use ALL_MODULES
my $modules = $prop_module_map{$self->prop} ?
$prop_module_map{$self->prop} :
\@all_modules;
#only do the map operation on the list we actually need to use
my @modules = map { join "::", $prefix, $_ } @$modules;
foreach my $module (@modules) {
require_module($module);
}
}
1;
__END__