http://qs1969.pair.com?node_id=634438

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

Dear monks,

there are some situations where I need to use/require a class from a variable (say $class). The way I do that is by:
eval "require $class"; if ( $@ ) { die "Can't load $class: $@"; } my $obj = $class->new;
...or something to that effect, i.e. by using a string eval. This feels wrong (perlcritic complains about it also). What's the right way?

Thanks!

Replies are listed 'Best First'.
Re: eval "require $class" seems wrong
by jZed (Prior) on Aug 22, 2007 at 17:37 UTC
    eval { require $class }; :-)

    update:

    eval { require "$class.pm" }; :-)
      That's not the same thing! Compare:
      perl -Mstrict -e 'my $c="CGI";eval "require $c";die $@ if $@' perl -Mstrict -e 'my $c="CGI";eval {require $c};die $@ if $@'
      Ah, the update works. I'd ++ that if I had votes left for today :)

        Then you are violating a different "best practice": Modules should always have a "::" in their name.

        - tye        

Re: eval "require $class" seems wrong
by ikegami (Patriarch) on Aug 22, 2007 at 17:48 UTC
      That also does a string eval, actually. See the _require sub in Module/Pluggable/Object.pm. Maybe I'm just barking up the wrong tree - I figured that something that's not uncommon (requiring a class at runtime, from a variable) could be done in a "best practices" sort of way. Apparently not. This seems strangely un-TIMTOWDI.

        I was kinda suggesting the best practice was to avoid requireing dynamic class names completely, and address the problem at a higher level.

        To answer your question directly, one can require either a class supplied as a bareword or a path. There isn't a way to require a class supplied in a scalar. In order to load a dynamically constructed class name, some use eval EXPR to do the former (e.g. Module::Pluggable), and some transform the class name into a path and use the latter (e.g. if). I have no idea why one cannot do require $class;.

Re: eval "require $class" seems wrong
by lima1 (Curate) on Aug 22, 2007 at 17:38 UTC
      It looks like what UNIVERSAL::require does is: turn $class name (e.g. Some::Class) into a path (Some/Class.pm), then do eval qq{require $path}, which amounts to the same thing - a string eval - but hidden in a dependency. Okay, it checks %INC first to see if it's already been loaded, but that's about it.

        If you change "::" to "/" and append ".pm", then you don't need a string eval, you can just do require $class. I wouldn't even do eval { require $class } (non-string eval) unless I was wanting to work around the module not being found. But if you do want to eval, then the best choice is:

        ( my $file= $class ) =~ s-::|'-/-g; if( ! eval { require "$file.pm"; 1 } ) { # work-around the failure here }

        But I don't find the argument against string eval compelling enough to have much of a strong preference between that and the below string eval unless there is risk that $class might contain mischevious data:

        if( ! eval "require $class; 1" ) { # work-around the failure here }

        If you don't want to work-around a failure (and trust $class), then the choices are:

        eval "require $class; 1" or die $@;

        and

        ( my $file= $class ) =~ s-::|'-/-g; require "$file.pm";

        And I'd never use UNIVERSAL::require, since I consider poluting such a very global namespace to be way too much sin for the sake of saving one line of code.

        - tye        

        It uses eval EXPR for #line to work. It has nothing to do with require. (eval BLOCK would suffice if you wanted to catch exceptions.)

        The following will work (even if your platform doesn't use / as the path seperator):

        $class = 'HTTP/Request.pm'; require $class;

        However, transforming the class name into a path is really no better.

        Sure, you need an eval here to check whether the module was found and successfully loaded. You need the eval in the first case to interpolate the module name and turn it into a bareword, or not?