in reply to Re: What is the best way to extend packages with global error handlers?
in thread What is the best way to extend packages with global error handlers?
Since I have no control over when the global variable will be set, I needed to do the copy of the original handler/installation of my handler immediately before each call to third party module subroutines. At first I thought that meant fairly repetitive hand-coding of wrapper subs for each sub that needed a custom handler. That seems to me a bit of a hack.
Then it occurred to me that I can avoid the hand-coding simply by require'ing rather than use'ing the package. Then, instead of importing, I would play around with the symbol table a bit in the begin block, like this:
For those who might be interested in this approach (or in case tilly has some ideas of an even better way to implement this), I've added a code sample below, in three parts. The first part is a dummy 3rd party class with a global variable as a handler. The second part is a sample extension module implementing tilly interception suggestion using generated wrappers. And the third part is a short demo showing that both the custom and global handler indeed get called properly even when the global handler is redefined long after all modules have been compiled and loaded.
Here is the dummy module Foo::Bar with a global handler
use warnings; use strict; package Foo::Bar; our $crHandler = sub { print "$_[0]: default handler called.\n"; }; sub doA1 { print "Doing A1 for $_[1]\n"; &$crHandler('A1'); } sub doB1 { print "Doing B1 for $_[1]\n"; &$crHandler('B1'); } sub doA2 { print "Doing A2 for $_[1]\n"; &$crHandler('A2'); } sub doB2 { print "Doing B2 for $_[1]\n"; &$crHandler('B2'); }
Here is a sample extension package for Foo::Bar that intercepts the input to the global handler without disrupting the end user's use of the global handler:
use warnings; use strict; package Foo::Baz; #----------------------------------------------- # import specially wrapped methods from Foo::Bar # that call custom handler and default to user # define global handler #----------------------------------------------- BEGIN { my $CLASS = __PACKAGE__; require Foo::Bar; # generates wrapper sub sub wrapAndCall { my $crCall = shift; my $crCustom = shift; my $crOriginal = $Foo::Bar::crHandler; local $Foo::Bar::crHandler = sub { return if (&$crCustom(@_)); if (defined($crOriginal)) { &$crOriginal(@_); } else { print "Original global handler undefined.\n"; } }; return &$crCall(@_); } # some custom handlers # return 1 if completely handled # return 0 if pass through to original global handler # for further processing my $crCustomHandler1 = sub { print "$_[0]: custom handler 1 called.\n"; return 0; }; my $crCustomHandler2 = sub { print "$_[0]: custom handler 2 called.\n"; return 0; }; # manufacture and assign the wrappers { no strict 'refs'; #import all of the subs using custom handler 1 foreach my $sMethod (qw(doA1 doB1)) { *{"${CLASS}::$sMethod"} = sub { return wrapAndCall(*{"Foo::Bar::$sMethod"} , $crCustomHandler1, @_); }; } #import all of the subs using custom handler 2 foreach my $sMethod (qw(doA2 doB2)) { *{"${CLASS}::$sMethod"} = sub { return wrapAndCall(*{"Foo::Bar::$sMethod"} , $crCustomHandler2, @_); }; } } } #----------------------------------------------- # define the rest of the methods #----------------------------------------------- sub new { return bless([], shift); } #----------------------------------------------- # module return #----------------------------------------------- return 1;
And here is a short demo script showing that this really works:
use warnings; use strict; use Foo::Baz; my $oFoo = Foo::Baz->new(); print "created \$oFoo=<$oFoo>\n"; print "\n**demoing default global handler**\n"; $oFoo->doA1('Ayelet (deer)'); print "\n**undefining global handler**\n"; $Foo::Bar::crHandler = undef; $oFoo->doA2('Mayan (spring)'); print "\n**reassigning global handler to non-default sub**\n"; $Foo::Bar::crHandler = sub { print "$_[0]: replacement handler called\n"; }; $oFoo->doB2('Dov (bear)');
Although this works, it isn't exactly a beginner Perl extension strategy. The implementation requires an understanding of localization, symbol tables, begin blocks, closures, and subroutine generators. Even as it solves my problem with global handlers as a customization design, it underscores why global variables as a customization strategy are not a good idea - unless, of course, I've missed an easier way to do this.
An ideal customization design would be something that preserved documented behavior, allowed for stable code, was non-disruptive, involved minimal or no cut-n-paste and could be used safely by a programmer with a basic knowledge of Perl.
All the same, this does solve my immediate problem, so
Many, many thanks for pointing the way, beth
|
|---|