Last month I wrote about using Moose’s override
function to, well, override a superclass’s method. Chris Prather on the #moose IRC channel suggested soon after that the around
method modifier (or its little sisters before
and after
) might be a better choice if you’re also calling the original method inside. He noted that “at a minimum override
only works if you’re subclassing, around
will apply to composed methods too.”
His point was that when you decide to compose roles (also know as traits) instead of or in addition to more traditional inheritance, override
simply doesn’t work: only a method modifier will do. (And as Graham Knop and Karen Etheridge later remarked on IRC, override
isn’t even an option if you’re using Moo as an alternative to Moose.)
Modifying a role’s method with around
might look like this:
#!/usr/bin/env perl
use v5.12; # for strict and say
use warnings;
package Local::Role::Hungry;
use Moose::Role;
requires 'name';
sub wants_food {
my $self = shift;
say $self->name, ' is hungry!';
return;
}
package Local::GuineaPig;
use Moose;
has name => (is => 'ro');
with 'Local::Role::Hungry';
around wants_food => sub {
my ($orig, $self) = splice @_, 0, 2;
say $self->name, ' runs to the front of the cage!';
$self->$orig(@_);
say 'Wheek!';
return;
};
package Local::Dog;
use Moose;
has name => (is => 'ro');
with 'Local::Role::Hungry';
around wants_food => sub {
my ($orig, $self) = splice @_, 0, 2;
say $self->name, ' runs to the kitchen!';
$self->$orig(@_);
say 'Woof!';
return;
};
before wants_food => sub {
my $self = shift;
say $self->name, ' is jumping!';
};
package main;
my $dog = Local::Dog->new(name => 'Seamus');
my @pigs = map { Local::GuineaPig->new(name => $_) }
qw<Cocoa Ginger Pepper>;
for my $animal ($dog, @pigs) {
$animal->wants_food();
}
Running the above yields:
Seamus runs to the kitchen!
Seamus is hungry!
Woof!
Cocoa runs to the front of the cage!
Cocoa is hungry!
Wheek!
Ginger runs to the front of the cage!
Ginger is hungry!
Wheek!
Pepper runs to the front of the cage!
Pepper is hungry!
Wheek!
It’s a little more involved than overriding a sub
, since method modifiers are passed both the consumed role’s original method ($orig
above) and the instance ($self
above) as parameters. It has the same effect, though, and you can call the original method by saying $self->$orig( parameters... )
. That’s why I used the splice
function so I could pass any remaining parameters as the original @_
array.
If all you want to do is have something happen either before or after the original method, just use before
or after
:
before wants_food => sub {
my $self = shift;
say $self->name, ' is jumping!';
};
Note that there’s no return value in a before
or after
modifier, as those are handled by the original method.
Modifiers aren’t limited to consuming classes; they can be in roles and modify their consumers’ methods. They also have a couple of other tricks:
- You can pass an array reference to modify multiple methods at once.
- You can use the contents of a variable to specify the modified method name, and use that same variable in the modifier itself.
- You can use a regular expression to select methods. (Beware if you’re using Moo that its Class::Method::Modifiers module doesn’t support this.)
Putting these together gives you constructs like these:
after qw<foo bar baz> => sub {
say 'Something got called';
};
for my $method_name (qw<foo bar baz>) {
before $method_name => sub {
say "Calling $method_name...";
};
}
before qr/^request_/ => sub {
my ($self, @args) = @_;
$self->is_valid(@args) or die 'Invalid arguments';
};
Moose comes with great introductory manuals for method modifiers and roles, so be sure to check those out. There’s a lot more to them and a blog can only cover so much.
Top comments (0)