http://qs321.pair.com?node_id=488877

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

Fellow Monks,

I have a module ( CGI::Application::Plugin::AutoRunmode ) that installs subroutine attribute handlers into the caller's namespace:

package MyApp; use base 'CGI::Application'; use CGI::Application::Plugin::AutoRunmode; sub my_run_mode : StartRunmode { # (the StartRunmode attribute is provided by the plugin) }
This works fine, unless some other module also tries to do the same thing (we would be overwriting each-others handlers). So I am now trying to find another way to implement these subroutine attributes, that is less risky to collide with other modules. My current idea is outlined below, but it does not work, raising my first question: Why does it not work? The second question would be for alternative approaches to the problem.

I thought I could avoid putting the attribute handlers into the caller's namespace by instead adding a package that contains these handlers to @ISA. Perldoc says:

When an attribute list is present in a declaration, a check is made to
       see whether an attribute 'modify' handler is present in the appropriate
       package (or its @ISA inheritance tree).
However, this sample code does not work: Package Three has two parent packages, One and Two. The attribute handler in package Two is never accessed. Am I missing something?
use strict; use warnings; { package One; sub MODIFY_CODE_ATTRIBUTES{ my ($pkg, $ref, @attr) = @_; print "Package A handler: @attr \n"; return @attr; } } { package Two; sub MODIFY_CODE_ATTRIBUTES{ my ($pkg, $ref, @attr) = @_; print "Package B handler: @attr\n"; return (); } } package Three; use base qw[ One Two]; sub foo : Eins Zwei Drei { } =========== $ perl /tmp/attr.pl Package A handler: Eins Zwei Drei Invalid CODE attributes: Eins : Zwei : Drei at /tmp/attr.pl line 30 BEGIN failed--compilation aborted at /tmp/attr.pl line 30.
Thanks for your time,

Thilo

Replies are listed 'Best First'.
Re: Package-specific Attribute Handling and Multiple Inheritence
by Roger (Parson) on Sep 03, 2005 at 14:12 UTC
    Quite a few years ago when I was still programming in C++, I found multiple inheritance of virtual functions a very tricky thing to handle.

    Say, if class C is derived from class A and class B, and provides an overload for virtual function, say, X, that exists in both A and B, then you have to explicitly say which parent you are going to inherit X from, otherwise it will result in compilation error.

    I see this particular problem similar to what I described above. Multiple inheritance of the MODIFY_CODE_ATTRIBUTES function. What is not declared explicitly in package Three is the member function MODIFY_CODE_ATTRIBUTES that is inherited from its parent, either A or B. Because Perl could not resolve the multiple inheritance, it simply picks the first package, One, as the parent class and ignores B.

    When the Perl compiler checks the attributes, it first invokes the MODIFY_CODE_ATTRIBUTES function declared in package Three, which is inherited from package One. And when the function returns a non-empty list, Perl assumes that the attributes could not be handled by the parent package as well as itself, and therefore raise the compilation error.

    I believe a remedy is to provide a MODIFY_CODE_ATTRIBUTES function in package Three that handles multiple inheritence explicitly.

    use strict; use warnings; package One; sub MODIFY_CODE_ATTRIBUTES{ my ($pkg, $ref, @attr) = @_; print "Package A handler: @attr \n"; return @attr; } package Two; sub MODIFY_CODE_ATTRIBUTES{ my ($pkg, $ref, @attr) = @_; print "Package B handler: @attr\n"; return (); } package Three; use base qw[ One Two ]; sub MODIFY_CODE_ATTRIBUTES{ my ($pkg, $ref, @attr) = @_; @attr = $_[0]->One::MODIFY_CODE_ATTRIBUTES($ref, @attr); if (@attr) { @attr = $_[0]->Two::MODIFY_CODE_ATTRIBUTES($ref, @attr); } print "Package C handler: @attr \n"; return @attr; } sub foo : Eins Zwei Drei { } __END__ $ perl attr.pl Package A handler: Eins Zwei Drei Package B handler: Eins Zwei Drei Package C handler:

Re: Package-specific Attribute Handling and Multiple Inheritence
by cees (Curate) on Sep 03, 2005 at 18:25 UTC

    Here are two other ways of dealing with it:

    First, in your base module, create a MODIFY_CODE_ATTRIBUTES method that manually walks up the inheritance tree calling MODIFY_CODE_ATTRIBUTES on all of the super classes.

    use strict; use warnings; package One; sub MODIFY_CODE_ATTRIBUTES { my ( $pkg, $ref, @attr ) = @_; print "Package One handler given (@attr) and handles 'Eins'\n"; return grep { $_ ne 'Eins' } @attr; } package Two; use base qw(One); sub MODIFY_CODE_ATTRIBUTES { my ( $pkg, $ref, @attr ) = @_; print "Package Two handler given (@attr) and handles 'Zwei'\n"; return grep { $_ ne 'Zwei' } @attr; } package Three; sub MODIFY_CODE_ATTRIBUTES { my ( $pkg, $ref, @attr ) = @_; print "Package Three handler given (@attr) and handles 'Drei'\n"; return grep { $_ ne 'Drei' } @attr; } package Four; sub empty {} package Five; use base qw[ Two Three Four ]; use Class::ISA; sub MODIFY_CODE_ATTRIBUTES { my ( $pkg, $ref, @attr ) = @_; foreach my $class (Class::ISA::super_path($pkg)) { @attr = $class->MODIFY_CODE_ATTRIBUTES( $ref, @attr ) if $class->can('MODIFY_CODE_ATTRIBUTES'); } print "These attributes are left over (@attr) \n" if @attr; return @attr; } sub foo : Eins Zwei Drei { } __END__

    Second, use NEXT to make sure all classes have a chance to run their MODIFY_CODE_ATTRIBUTES method. This depends on all classes working together to make sure they all use and call NEXT.

    use strict; use warnings; package One; use NEXT; sub MODIFY_CODE_ATTRIBUTES { my ( $pkg, $ref, @attr ) = @_; print "Package One handler given (@attr) and handles 'Eins'\n"; @attr = grep { $_ ne 'Eins' } @attr; return $pkg->NEXT::MODIFY_CODE_ATTRIBUTES($ref, @attr); } package Two; use base qw(One); use NEXT; sub MODIFY_CODE_ATTRIBUTES { my ( $pkg, $ref, @attr ) = @_; print "Package Two handler given (@attr) and handles 'Zwei'\n"; @attr = grep { $_ ne 'Zwei' } @attr; return $pkg->NEXT::MODIFY_CODE_ATTRIBUTES($ref, @attr); } package Three; use NEXT; sub MODIFY_CODE_ATTRIBUTES { my ( $pkg, $ref, @attr ) = @_; print "Package Three handler given (@attr) and handles 'Drei'\n"; @attr = grep { $_ ne 'Drei' } @attr; return $pkg->NEXT::MODIFY_CODE_ATTRIBUTES($ref, @attr); } package Four; sub empty {} package Five; use base qw[ Two Three Four ]; sub foo : Eins Zwei Drei { } __END__

    Update: I should have noted that neither of these options are ideal... The first could potentially call the same MODIFY_CODE_ATTRIBUTES method in one class two times, if one of the modules already calls SUPER::MODIFY_CODE_ATTRIBUTES (not sure if that would break things, probably not). Also, a module will not have the ability to override the MODIFY_CODE_ATTRIBUTES method of it's parent class, since the parent class version will still be called... The second version requires you to trust that all the modules you inherit from that have a MODIFY_CODE_ATTRIBUTES method also use the NEXT method otherwise something might get missed.

Re: Package-specific Attribute Handling and Multiple Inheritence
by xdg (Monsignor) on Sep 06, 2005 at 11:57 UTC

    I've been wrestling mentally with this same issue for improving Object::LocalVars. The best thing I've come up with it rewriting my attributes to use Attribute::Handlers, which provides the MODIFY_CODE_ATTRIBUTES subroutine and farms out the actual attributes found to other subroutines that registered themselves with Attribute::Handlers. This, of course, requires everyone to play nice and all use Attribute::Handlers as well.

    I avoided Attribute::Handlers at first because of the complexity within it -- I wanted more direct control of the attribute handling process to be sure I understood what was happening. But for interoperability, I think it's the right way to go. However, Attribute::Handlers tries to do a lot of stuff in the CHECK phase by default, and that will break under mod_perl. I believe that Attribute::Handlers will allow one to force handling to be immediate in the BEGIN phase, which I what I intend to explore as an approach.

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

      Thanks, I have started looking at Attribute::Handlers myself in the meantime. It does look a little scary inside, but I guess that cannot be avoided when dealing with attributes, and as you say, with its centralized handler that just dispatches to the actual attribute handlers it is quite interoperable when many modules try to register attributes.

      Since this is for CGI::Application, it would have to play nice with mod_perl, though ... (I have recently even had a bug report from someone using Apache::Reload, which makes things even more tricky)

        Just make sure your handlers all run in the BEGIN phase. That will limit some of what you can do with them -- for example, you can get the name of a subroutine, but the code reference won't yet be defined. But that's no different than writing your own attribute handling code, so you've probably already dealt with it.

        -xdg

        Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.