black deer lying on plants near green trees during daytime

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, @args) = @_;
say $self->name, ' runs to the front of the cage!';
$self->$orig(@args);
say 'Wheek!';
return;
};

package Local::Dog;
use Moose;
has name => (is => 'ro');
with 'Local::Role::Hungry';

around wants_food => sub {
my ($orig, $self, @args) = @_;
say $self->name, ' runs to the kitchen!';
$self->$orig(@args);
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).

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.