In a previous blog I discovered Class::Load and its awesomeness.
Here is one practical application of it:
--> UPDATES
--> UPDATES2
Automatic Optional Requisites
Say you have a library which provides some form of extensibility to consuming modules, and you want a way to "magically" discover a class to use, but use a fallback if its not there.
Here is how to do it with Class::Load
use strict; use warnings; package Some::Module; use Class::Load qw( :all ); sub do_setup { ... } sub import { my $caller = caller(); my $maybemodule = "$caller::Controller"; if( try_load_class( $maybemodule ) ){ do_setup( $maybemodule ); # its there, and it works. } else { if ( $Class::Load::ERROR =~ qr/Can't locate \Q$maybemodule\E in \@INC/ ){ do_setup("Some::Module::Default"); } else { die $Class::Load::ERROR; } } }To do it the right way without Class::Load is extraordinarily complicated.
use strict; use warnings; package Some::Module; sub do_setup { ... } sub import { my $caller = caller(); my $maybemodule = "$caller::Controller"; # see rt.perl.org #19213 my @parts = split '::', $class; my $file = $^O eq 'MSWin32' ? join '/', @parts : File::Spec->catfile(@parts); $file .= '.pm'; my $error; my $success; { local $@; $success = eval { local $SIG{__DIE__} = 'DEFAULT'; require $file; 'success'; }; $error = $@; } if( $success eq 'success' ) ){ do_setup( $maybemodule ); # its there, and it works. } else { if ( $error =~ qr/Can't locate \Q$maybemodule\E in \@INC/ ){ do_setup("Some::Module::Default"); } else { die $error; } } }
And even then, you still have a handful of sneaky bugs lurking in there :/
- With the second code, if somebody dynamically created the ::Controller class and didn't create a file for it, it will not work properly, and they'll have to tweak $INC somewhere for it to work
- If somebody loaded the ::Controller class manually before hand, but it failed, and they didn't report the error, on 5.8, the above code will behave as if the code Did load successfully. ( Truely nasty )
Class::Load has a lot of heuristics in it to try avoid both these situations ( well, the latter one will be soon once a 1-line patch goes in )
There are a few things still that I don't like doing that way, but for now, that's the best I can get
- Using a regular expression to determine what type of load failure occurred is nasty, but the only alternative approaches are either
- more complicated
- prone to be wrong on 5.8
What I'd like to be able to do
and may write a patch for
use strict; use warnings; package Some::Module; use Class::Load qw( :all ); sub do_setup { ... } sub import { my $caller = caller(); my $maybemodule = "$caller::Controller"; if( try_load_working_class( $maybemodule ) ){ do_setup( $maybemodule ); # its there, and it works. } else { do_setup("Some::Module::Default"); #its not there. } }
The idea being "Syntax errors are syntax errors, there's no good reason to suppress them , at all", so in the above code, if Whatever::Controller existed, but was broken, it would die, instead of treating it as if it were absent.
UPDATE
Module Patched and on github! =). Waiting on an authoritative update =)
package App; use Class::Load qw( :all ); sub import { my $caller = caller(); my $baseclass = load_optional_class("${caller}::Controller") ? "${caller}::Controller" : "App::Controller"; push @{$caller}::ISA, $base_class; # this line is pseudocode. }
UPDATE #2
On CPAN: http://search.cpan.org/~sartak/Class-Load-0.06/lib/Class/Load.pm.
Thanks Sartak =)