Class::Monkey - Monkey Patch a class/instance with modifiers and other sweet stuff
Say we have a module installed on the system. It does some handy things, but you find a bug or a strange feature. We can easily fix it without subclassing by the following...
# StupidClass.pm package SupidClass; sub new { my $class = shift; return bless {}, $class; } sub name { my ($self, $name) = @_; print "Hello, ${name}\n"; } sub no_args { print "No arguments were specified!\n"; } 1;
Above is our class. A stupid one at that. The name method doesn't validate the arguments.. it just tries to print them in a 'hello' string. We can use an around method to call the name method if arguments are passed, or to call no_args if not. We can happily do this from the program.
name
around
no_args
# our_program.pl use Class::Monkey qw<StupidClass>; # The patch around 'name' => sub { my $method = shift; my $self = shift; if (@_) { $self->$method(@_); } else { $self->no_args(); } }, qw<StupidClass>; # /The Patch $s->name(); # actually executes no_args $s->name("World"): # runs name
Simply import the classes you want to patch as an array when you use Class::Monkey. Doing this means you won't even need to use the module you want to patch - Class::Monkey takes care of that for you.
use Class::Monkey
use
use Class::Monkey qw<Some::Package Another::Module>; method 'needThisMethod' => sub { ... }, qw<Some::Package>; my $p = Some::Package->new; $p->needThisMethod;
Please see tweak for more information on how to get this method. haz behaves the exact same way as extends.
tweak
haz
extends
use Class::Monkey '-tweak'; haz 'FooClass'; haz qw<FooClass Another::FooClass More::FooClass>;
This method is only available when you use Class::Monkey '-tweak'. This option may be preferred over the default modifier methods when you need to patch a class from a script using Moose/Mouse/Moo/Mo, etc. When you add -tweak, it will export only the tweak and haz methods.
use Class::Monkey '-tweak'
use Class::Monkey '-tweak'; haz 'Foo'; tweak 'mymethod' => ( class => 'Foo', override => sub { print "mymethod has been overridden\n"; }, );
You can replace 'override' in the above example with any of the available Class::Monkey modifiers (ie: before, method, after, around). Also class can be the full name of the class as above, or an instance.
class
Tells Class::Monkey you want to be able to patch the specified modules, but not to 'use' them.
use Class::Monkey; use MyFoo; canpatch qw<MyFoo AndThis AndThat>; # then do stuff with MyFoo as normal
Have a subroutine in your file you want to explort to your patched class? Use exports to do so.
exports
package Foo; sub new { return bless {}, __PACKAGE__ } 1; # test.pl package MyPatcher; use Class::Monkey qw<Foo>; sub foo { print "Hiya\n"; } exports 'foo', qw<Foo>; my $foo = Foo->new; $foo->foo(); # prints Hiya exports 'foo', $foo; # works with instances too
Sometimes you might not want to include the module you want to patch when you use Class::Monkey. No problem. You can use extends to do it later on.
use Class::Monkey; extends 'SomeClass'; extends qw<SomeClass FooClass>;
Gives the wanted class an accessor. You can assign it a read-only or read-writable type (Similar to Moose). Because it works on remote packages you need to give it the full name of the method including the class.
use Class::Monkey qw<Foo::Class>; has 'Foo::Class::greet' => ( is => 'ro', default => 'Hello' ); # read-only has 'Foo::Class::name' => ( is => 'rw', default => 'World' ); # read-writable my $foo = Foo::Class->new; say "Hello, " . $foo->name; $foo->name('Monkey); # updates the name accessor to return a new value
If you leave out the is parameter when you define an accessor it will always default to read-writable.
is
Note This method should be deprecated as all modifiers now support constants OR an instance. Class::Monkey will determine which method should be used, so calling instance is no longer required.
instance
Patch an instance method instead of an entire class
# Pig.pm package Pig; sub new { return bless {}, shift; } sub says { print "Oink!\n"; } # test.pl package main; use Class::Monkey qw<Pig>; my $pig = Pig->new; my $pig2 = Pig->new; instance 'says' => sub { print "Meow\n"; }, $pig2; # only $pig2 will have its says method overridden
As of 0.002 you can now do it like this
override 'says' => sub { print "Meow\n"; }, $pig2; before 'says' => sub { print "Going to speak\n"; }, $pig;
etc..
If you want to run the original version of a patched method, but not unpatch it right away you can use original to do so. It will run the old method before it was patched with any arguments you specify, but the actual method will still remain patched.
original
after 'someMethod' => sub { print "Blah\n" }, qw<Foo>; original('Foo', 'someMethod', qw<these are my args>);
OR if you prefer, you can just call Class::Monkey::PatchedClassName::method-(@args)>
Class::Monkey::PatchedClassName::method-
Class::Monkey::Foo->someMethod('these', 'are', 'my', 'args);
Overrides an already existing method. If the target method doesn't exist then Class::Monkey will throw an error.
override 'foo' => sub { return "foo bar"; }, qw<Some::Module>;
Creates a brand new method in the target module. It will NOT allow you to override an existing one using this, and will throw an error.
method 'active_customers' => sub { my $self = shift; return $self->search({ status => 'active' }); }, qw<Schema::ResultSet::Customer>;
Simply adds code to the target method before the original code is ran
# Foo.pm package Foo; sub new { return bless {}, __PACKAGE__; } sub hello { print "Hello, $self->{name}; } 1; # test.pl use Class::Monkey qw<Foo>; my $foo = Foo->new; before 'hello' => { my $self = shift; $self->{name} = 'World'; }, qw<Foo>; print $foo->hello . "\n";
Basically the same as before, but appends the code specified to the END of the original
before
Around gives the user a bit more control over the subroutine. When you create an around method the first argument will be the original method, the second is $self and the third is any arguments passed to the original subroutine. In a away this allows you to control the flow of the entire subroutine.
$self
package MyFoo; sub greet { my ($self, $name) = @_; print "Hello, $name!\n"; } 1; # test.pl use Class::Monkey qw<MyFoo>; # only call greet if any arguments were passed to MyFoo->greet() around 'greet' => sub { my $method = shift; my $self = shift; $self->$method(@_) if @_; }, qw<MyFoo>;
Undoes any modifications made to patched methods, restoring it to its original state.
override 'this' => sub { print "Blah\n"; }, qw<FooClass>; unpatch 'this', 'FooClass';
Brad Haywood <brad@geeksware.net>
You may distribute this code under the same terms as Perl itself.
To install Class::Monkey, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Class::Monkey
CPAN shell
perl -MCPAN -e shell install Class::Monkey
For more information on module installation, please visit the detailed CPAN module installation guide.