views:

259

answers:

2

I'm using Moose and I need to wrap method calls in my project. It's important that my wrapping code be the most outer modifier. What I've done so far is put my method modifiers in a Moose Role and then applied that role at the end of my class like this:

use Moose::Util;
Moose::Util::apply_all_roles(__PACKAGE__->meta, ('App:Roles::CustomRole'));
__PACKAGE__->meta->make_immutable;

This allows me to be reasonably sure that my my role's modifiers are defined last, therefore giving me the correct behavior for "before" and "after." (The "before" and "after" in the role are called very first and very last.)

I originally thought this would be sufficient, but I now really need to wrap methods in a similar way with "around." Class::MOP, which Moose is built on, applies "around" modifiers very first, therefore they're called after "before" and before "after."

For more detail, here is the current calling order of my modifiers:

CUSTOM ROLE before
    before 2
       before 1
           CUSTOM ROLE around
               around
                   method
               around
           CUSTOM ROLE around
       after 1
    after 2
 CUSTOM ROLE AFTER

I really need something like this:

CUSTOM ROLE before
    CUSTOM ROLE around
        before 2
           before 1
               around
                   method
               around
           after 1
        after 2
    CUSTOM ROLE around
 CUSTOM ROLE AFTER

Any ideas on how to get my "around" modifier to be applied / called where I want it to? I know I could do some symbol table hacking (like Class::MOP is already doing) but I'd really rather not.

+2  A: 

I'm fairly new to Moose, but why do you do this:

use Moose::Util;
Moose::Util::apply_all_roles(__PACKAGE__->meta, ('App:Roles::CustomRole'));

rather than simply this?

with 'App:Roles::CustomRole';

Regarding your question, it's a bit of a hack, but could you split your around method into before and after methods and apply the role at the end of your class definition (so it is applied in your desired order)? You could use private attributes to save state between the two methods if absolutely necessary.

Ether
The problem is that neither `before` nor `after` let you (cleanly) alter the return semantics the way `around` will. If these semantics are important, you're screwed. If they're not, why are you using `around` to begin with?
perigrin
The reason I apply it manually is that the modifiers are added (and then run) in the order they are defined. Generally you don't care about the order modifiers are run. But, it my case I wanted my role's modifiers to be the very first before and very last after. If you use the "with" syntax to apply the roles its modifiers are defined first and so they'll be run as the inner most "before" and "after." By applying the role manually at the end they are defined last and are therefore run when I want them to be.
Matt Wood
@Matt: this does indeed seem to be a shortcoming of Moose. Perhaps the `after` semantics should allow altering the return value as `around` does, or the ordering of the `before`, `around` and `after` methods should be modified so they are all LIFO with respect to each other, rather than just to themselves (if that makes sense).
Ether
`before` and `after` are designed *intentionally* to not contain side effects like this. Specifically `before` and `after` are supposed to be for out-of-band behaviors attached to the method, while `around` is for just this kind o method "overriding". Altering three year old semantics like that is really not something the Moose team would accept, nor do I think it's really the answer.
perigrin
+3  A: 

Simplest solution is to have CUSTOM ROLE define a method that calls the main method and then wrap that.

role MyRole { 
    required 'wrapped_method';
    method custom_role_base_wrapper { $self->wrapped_method(@_) }

    around custom_role_base_wrapper { ... }
    before custom_role_base_wrapper { ... }
}

The problem you're having is that you're trying to have the CUSTOM ROLE around wrap something other than a method. Which is not what it is designed to do. Other than writing similar symbol table hackery like you've suggested (probably you could argue one of the Moose people into exposing an API in Class::MOP to help get there), the only other solution I can think of is the one above.

If you don't want the extra call stack frame that custom_role_base_wrapper will add, you should look at Yuval's Sub::Call::Tail or using goto to manipulate the call stack.

perigrin
I ended up going with the custom wrapper idea. It gave me the flexibility I needed, thanks.
Matt Wood