***The following is background to help explain what I've tried so far. If you'd prefer to read the main question first, skip to the bottom.***
My Baz
module invokes a number of other modules, all similar, each of which is one level down in the namespace. The ones of interest here compose the Thing
role. In addition to the individual require
statements, the constant list ALL_THINGS
enumerates the relevant Thing
modules for use later on. My original code looks like so:
package Foo::Bar::Baz;
use constant ALL_THINGS => qw{ Foo::Bar::Baz::ThingA Foo::Bar::Baz::ThingB ... };
require Foo::Bar::Baz::ThingA;
require Foo::Bar::Baz::ThingB;
[...]
As I mentioned, there are quite a lot of Thing
modules, and I'm still adding more. Each time I create a new Thing
class, I have to add a new require
statement and also add the same identical text to the ALL_THINGS
list. In order to avoid this duplication, I wanted to replace the individual require
lines with a loop iterating over ALL_THINGS
. I added this, which works fine by itself:
foreach my $module (ALL_THINGS) {
eval "require $module";
}
However this solution doesn't seem to play well with my next change.
The full module name for each Thing
is long and unwieldy. I'd like to alias the package name to make it easier to type/read. I looked at Package::Alias
, but it seems that will use
them, which I'd like to avoid if possible. The best solution I've come to so far is the pattern suggested in this question:
BEGIN { *Things:: = *Foo::Bar::Baz:: ; }
This also works, in the sense that it allows me to use Thing::ThingA->classMethod
. However, unsurprisingly, it doesn't work in the require
loop above, as require Thing::ThingA
searches @INC
for Thing/ThingA.pm
rather than Foo/Bar/Baz/ThingA.pm
.
I'd like to cut down the long package names (i.e. Foo::Bar::Baz::ThingA
) in my ALL_THINGS
list to Things::ThingA
, but still be able to use that same list to build my require
statements in a loop.
Foo::Bar::Baz::
as Things::
such that I can require Things::ThingA
?Things::ThingA
to Foo::Bar::Baz::ThingA
in (or before?) the eval so that require
finds the correct package?Bonus Questions (related to eval "require $x"
):
eval
? require
statements for each module)?Note: I accepted Dave Sherohman's answer, as it most fully addresses the question I asked. However, I ultimately implemented a solution based on lordadmira's answer.
An “alias” package is a symbolic name (reference) for another package (target).
In perl namespaces are called "packages" and the package declaration tells the compiler which namespace to prefix to our variables and unqualified dynamic names.
The perl1 compiler (yes, there is a compiler although it's interpreted language) loads files, compiles them, then switches to run time to run the compiled code. When it encounters a new file to load, it switches back to compile time, compiles the new code, and so on. To load a module at compile time, you use it.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With