Search code examples
perlmoose

Method modifiers and glob assignments


Sometimes Moose's method modifiers do not play well with symbol table entries created by other packages that try to do Moose-like things their own way.

I am working with some older code that follows this pattern:

package MethodCreator;

sub make_some_method {
    my $caller = caller();
    *{$caller . '::generated_method'} = sub { print 'I am a generated method' }
}

1;

The intent of the MethodCreator package is to add some standard definitions to multiple consumer packages, and it implements this via direct glob assignment. The problem is, these created methods do not play well with Moose's method modifiers:

package Consumer;

use Moose;
use MethodCreator;

MethodCreator::make_some_method();

# The following line causes compilation to fail                                                                                                                                                              
# before generated_method => sub { print 'About to call a generated method: ' };                                                                                                                             

generated_method();

1;

As the comment indicates, an attempt to use a method modifier on one of these dynamically-added subroutines results in a compile time error ("generated_method is not in the inheritance hierarchy").

It will not be practical to change or replace MethodCreator (as much as that may be the "right solution"). So the question is: how can package Consumer be changed to make the 'before' modifier play well with such subroutines, i.e. to behave as you would expect if 'generated_method' were defined directly within Consumer?


Solution

  • You can use Class::Method::Modifiers to do that instead of using the built-in Moose modifiers. Make sure you do not import anything from them, though, or you will get redefined warnings. Instead, call before explicitly from the Class::Method::Modifiers package.

    package MethodCreator;
    use strict;
    use warnings;
    no strict 'refs';
    
    sub make_some_method {
        my $caller = caller();
        *{$caller . '::generated_method'} = sub { print 'I am a generated method' }
    }
    
    package Consumer;
    
    use Moose;
    use Class::Method::Modifiers ();
    
    MethodCreator::make_some_method();
    
    # This one works
    Class::Method::Modifiers::before( generated_method => sub { print 'About to call a generated method: ' });
    
    generated_method();
    
    1;
    

    Output:

    About to call a generated method: I am a generated method
    

    Now why does this work?

    The C::M::M docs say this:

    Note that the syntax and semantics for these modifiers is directly borrowed from Moose (the implementations, however, are not).

    Simplified, this overwrites the sub in the package with its own sub, which does your stuff and calls the original one afterwards.

    In Moose on the other hand those are implemented in Class::MOP::Method::Wrapped, and they use the MOP to handle all the fancy inheritance stuff as well as multiple modifiers. But because you don't have those with your 'manually generated' subroutine/method, they will not work.