File Coverage

blib/lib/Class/Monkey.pm
Criterion Covered Total %
statement 59 277 21.3
branch 4 70 5.7
condition 0 11 0.0
subroutine 18 46 39.1
pod 14 15 93.3
total 95 419 22.6


line stmt bran cond sub pod time code
1             package Class::Monkey;
2              
3 1     1   23615 use strict;
  1         2  
  1         42  
4 1     1   5 use warnings;
  1         2  
  1         36  
5 1     1   4 no warnings 'redefine';
  1         6  
  1         454  
6              
7             our $VERSION = '0.007';
8             $Class::Monkey::Subs = {};
9             $Class::Monkey::CanPatch = [];
10             $Class::Monkey::Classes = [];
11             $Class::Monkey::Iter = 0;
12              
13             =head1 NAME
14              
15             Class::Monkey - Monkey Patch a class/instance with modifiers and other sweet stuff
16              
17             =head1 DESCRIPTION
18              
19             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...
20              
21             # StupidClass.pm
22             package SupidClass;
23            
24             sub new {
25             my $class = shift;
26             return bless {}, $class;
27             }
28              
29             sub name {
30             my ($self, $name) = @_;
31             print "Hello, ${name}\n";
32             }
33              
34             sub no_args {
35             print "No arguments were specified!\n";
36             }
37              
38             1;
39              
40             Above is our class. A stupid one at that. The C method doesn't validate the arguments.. it just tries to print them in a 'hello' string.
41             We can use an C method to call the C method if arguments are passed, or to call C if not. We can happily do this from the program.
42              
43             # our_program.pl
44             use Class::Monkey qw;
45              
46             # The patch
47             around 'name' => sub {
48             my $method = shift;
49             my $self = shift;
50            
51             if (@_) {
52             $self->$method(@_);
53             }
54             else {
55             $self->no_args();
56             }
57             },
58             qw;
59             # /The Patch
60            
61             $s->name(); # actually executes no_args
62             $s->name("World"): # runs name
63              
64             =head1 SYNOPSIS
65              
66             Simply import the classes you want to patch as an array when you C. Doing this means you won't even need to C the module you want to patch - Class::Monkey takes care of that for you.
67              
68             use Class::Monkey qw;
69              
70             method 'needThisMethod' => sub {
71             ...
72             },
73             qw;
74              
75             my $p = Some::Package->new;
76             $p->needThisMethod;
77              
78             =head1 METHODS
79              
80             =cut
81              
82             sub import {
83 1     1   13 my ($class, @args) = @_;
84 1         4 my $pkg = scalar caller;
85 1         3 my $tweak = 0;
86 1 50       5 if (scalar @args > 0) {
87 0         0 for my $m (@args) {
88 0 0       0 if ($m eq '-tweak') {
89 0         0 $tweak = 1;
90 0         0 my ($index) = grep { $args[$_] eq '-tweak' } 0..$#args;
  0         0  
91 0         0 splice @args, $index, 1;
92 0         0 next;
93             }
94 0         0 push @{$Class::Monkey::CanPatch}, $m;
  0         0  
95             }
96 0         0 _extend_class(\@args, $pkg);
97             }
98              
99             _import_def(
100 1 50       7 $pkg,
101             undef,
102             qw/
103             override
104             method
105             before
106             after
107             around
108             unpatch
109             instance
110             original
111             has
112             extends
113             exports
114             canpatch
115             /
116             ) unless $tweak;
117              
118 1 50       15 _import_def($pkg, undef, qw) if $tweak;
119             }
120              
121             sub _extend_class {
122 0     0   0 my ($mothers, $class) = @_;
123              
124 0 0       0 return if $class eq __PACKAGE__;
125 0         0 foreach my $mother (@$mothers) {
126             # if class is unknown to us, import it (FIXME)
127 0 0       0 unless (grep { $_ eq $mother } @$Class::Monkey::Classes) {
  0         0  
128 0         0 eval "use $mother";
129 0 0       0 warn "Could not load $mother: $@"
130             if $@;
131              
132 0         0 $mother->import;
133             }
134 0         0 push @$Class::Monkey::Classes, $mother;
135             }
136              
137             {
138 1     1   7 no strict 'refs';
  1         2  
  1         225  
  0         0  
139 0         0 @{"${class}::ISA"} = @$mothers;
  0         0  
140             }
141             }
142              
143             =head2 haz
144              
145             Please see C for more information on how to get this method. C behaves the exact same way as C.
146              
147             use Class::Monkey '-tweak';
148              
149             haz 'FooClass';
150             haz qw;
151              
152             =cut
153              
154             sub haz {
155 0     0 1 0 my (@args) = @_;
156 0         0 my $pkg = getscope();
157 0 0       0 if (scalar @args > 0) {
158 0         0 for my $m (@args) {
159 0         0 push @{$Class::Monkey::CanPatch}, $m;
  0         0  
160             }
161 0         0 _extend_class(\@args, $pkg);
162             }
163             }
164              
165             =head2 tweak
166              
167             This method is only available when you C. 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 C and C methods.
168              
169             use Class::Monkey '-tweak';
170             haz 'Foo';
171              
172             tweak 'mymethod' => (
173             class => 'Foo',
174             override => sub {
175             print "mymethod has been overridden\n";
176             },
177             );
178              
179             You can replace 'override' in the above example with any of the available Class::Monkey modifiers (ie: before, method, after, around). Also C can be the full name of the class as above, or an instance.
180              
181             =cut
182            
183             sub tweak {
184 0     0 1 0 my ($sub, %args) = @_;
185            
186 0         0 my $class = delete $args{class};
187             {
188 1     1   6 no strict 'refs';
  1         2  
  1         93  
  0         0  
189 0         0 foreach my $action (keys %args) {
190 0         0 $action->($sub, $args{$action}, $class);
191             }
192             }
193             }
194              
195             sub _import_def {
196 1     1   5 my ($pkg, $from, @subs) = @_;
197 1     1   5 no strict 'refs';
  1         2  
  1         333  
198 1 50       4 if ($from) {
199 0         0 for (@subs) {
200 0         0 *{$pkg . "::$_"} = \&{"$from\::$_"};
  0         0  
  0         0  
201             }
202             }
203             else {
204 1         3 for (@subs) {
205 12         27 *{$pkg . "::$_"} = \&$_;
  12         67  
206             }
207             }
208             }
209              
210             sub _doh {
211 0     0     my $err = shift;
212 0           die $err . "\n";
213             }
214              
215             sub _check_init {
216 0     0     my $class = shift;
217              
218 0 0         $class = ref($class) if ref($class);
219 0 0         _doh "No class was specified" if ! $class;
220              
221 0           _doh "Not allowed to patch $class"
222 0 0         if ! grep { $_ eq $class } @{$Class::Monkey::CanPatch};
  0            
223             }
224              
225             =head2 canpatch
226              
227             Tells Class::Monkey you want to be able to patch the specified modules, but not to 'use' them.
228              
229             use Class::Monkey;
230             use MyFoo;
231              
232             canpatch qw;
233              
234             # then do stuff with MyFoo as normal
235              
236             =cut
237              
238             sub canpatch {
239 0     0 1   my (@modules) = @_;
240            
241 0           push @{$Class::Monkey::CanPatch}, @modules;
  0            
242             }
243              
244             sub _add_to_subs {
245 0     0     my $sub = shift;
246 0 0         if (! exists $Class::Monkey::Subs->{$sub}) {
247 0           $Class::Monkey::Subs->{$sub} = {};
248 0           $Class::Monkey::Subs->{$sub} = \&{$sub};
  0            
249 1     1   6 no strict 'refs';
  1         1  
  1         251  
250 0           *{__PACKAGE__ . "::$sub"} = \&{$sub};
  0            
  0            
251             }
252             }
253              
254             sub getscope {
255 0     0 0   my $self = shift;
256 0   0       my $pkg = $self||scalar caller(1);
257 0           return $pkg;
258             }
259              
260             =head2 exports
261              
262             Have a subroutine in your file you want to explort to your patched class? Use C to do so.
263              
264             package Foo;
265              
266             sub new { return bless {}, __PACKAGE__ }
267              
268             1;
269              
270             # test.pl
271             package MyPatcher;
272              
273             use Class::Monkey qw;
274            
275             sub foo { print "Hiya\n"; }
276              
277             exports 'foo', qw;
278             my $foo = Foo->new;
279             $foo->foo(); # prints Hiya
280            
281             exports 'foo', $foo; # works with instances too
282              
283             =cut
284              
285             sub exports {
286 0     0 1   my ($method, $class) = @_;
287 0           my $pkg = caller;
288 1     1   6 no strict 'refs';
  1         2  
  1         349  
289 0 0         if (ref($class)) {
290 0           $Class::Monkey::Iter++;
291 0           my $package = ref($class) . '::Class::Monkey::' . $Class::Monkey::Iter;
292 0           @{$package . '::ISA'} = (ref($class));
  0            
293 0           *{"${package}::${method}"} = *{"${pkg}::${method}"};
  0            
  0            
294 0           bless $_[1], $package;
295             }
296             else {
297 0           *{"${class}::${method}"} = *{"${pkg}::${method}"};
  0            
  0            
298             }
299             }
300              
301            
302             =head2 extends
303              
304             Sometimes you might not want to include the module you want to patch when you C. No problem. You can use C to do it later on.
305              
306             use Class::Monkey;
307             extends 'SomeClass';
308             extends qw;
309              
310             =cut
311              
312             sub extends {
313 0     0 1   my (@args) = @_;
314 0           my $pkg = getscope;
315 0 0         if (scalar @args > 0) {
316 0           for my $m (@args) {
317 0           push @{$Class::Monkey::CanPatch}, $m;
  0            
318             }
319 0           _extend_class(\@args, $pkg);
320             }
321             }
322              
323             =head2 has
324              
325             Gives the wanted class an accessor. You can assign it a read-only or read-writable type (Similar to Moose).
326             Because it works on remote packages you need to give it the full name of the method including the class.
327              
328             use Class::Monkey qw;
329            
330             has 'Foo::Class::greet' => ( is => 'ro', default => 'Hello' ); # read-only
331             has 'Foo::Class::name' => ( is => 'rw', default => 'World' ); # read-writable
332            
333             my $foo = Foo::Class->new;
334             say "Hello, " . $foo->name;
335            
336             $foo->name('Monkey); # updates the name accessor to return a new value
337              
338             If you leave out the C parameter when you define an accessor it will always default to read-writable.
339              
340             =cut
341              
342             sub has {
343 0     0 1   my ($name, %args) = @_;
344 0   0       my $rtype = delete $args{is}||"";
345 0   0       my $default = delete $args{default}||"";
346 1     1   16 no strict 'refs';
  1         1  
  1         333  
347 0 0         if ($rtype eq 'ro') {
348 0 0         if (! $default) {
349 0           warn "Redundant null static accessor '$name'";
350             }
351 0           *{$name} = sub {
352 0     0     my ($self, $val) = @_;
353 0 0         if (@_ == 2) {
354 0           warn "Cannot alter a Read-Only accessor";
355 0           return ;
356             }
357 0           return $default;
358 0           };
359             }
360             else {
361 0           *{$name} = sub {
362 0     0     my ($self, $val) = @_;
363 0 0 0       if ($default && ! $self->{_used}->{$name}) {
364 0           $self->{$name} = $default;
365 0           $self->{_used}->{$name} = 1;
366             }
367 0 0         if (@_ == 2) {
368 0           $self->{$name} = $val;
369             }
370             else {
371 0   0       return $self->{$name}||"";
372             }
373 0           };
374             }
375             }
376             # modifiers
377              
378             =head2 instance
379              
380             B 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 C is no longer required.
381              
382             Patch an instance method instead of an entire class
383              
384             # Pig.pm
385             package Pig;
386             sub new { return bless {}, shift; }
387             sub says { print "Oink!\n"; }
388              
389             # test.pl
390             package main;
391             use Class::Monkey qw;
392              
393             my $pig = Pig->new;
394             my $pig2 = Pig->new;
395             instance 'says' => sub {
396             print "Meow\n";
397             },
398             $pig2;
399              
400             # only $pig2 will have its says method overridden
401              
402             As of 0.002 you can now do it like this
403              
404             override 'says' => sub {
405             print "Meow\n";
406             }, $pig2;
407              
408             before 'says' => sub {
409             print "Going to speak\n";
410             }, $pig;
411              
412             etc..
413              
414             =cut
415              
416             sub instance {
417 0     0 1   my($method, $code, $instance) = @_;
418 0           $Class::Monkey::Iter++;
419 0           my $package = ref($instance) . '::Class::Monkey::' . $Class::Monkey::Iter;
420 1     1   6 no strict 'refs';
  1         2  
  1         321  
421 0           @{$package . '::ISA'} = (ref($instance));
  0            
422 0           *{$package . '::' . $method} = $code;
  0            
423 0           bless $_[2], $package;
424             }
425              
426             =head2 original
427              
428             If you want to run the original version of a patched method, but not unpatch it right away
429             you can use C 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.
430              
431             after 'someMethod' => sub {
432             print "Blah\n"
433             },
434             qw;
435              
436             original('Foo', 'someMethod', qw);
437              
438             OR if you prefer, you can just call C(@args)>
439              
440             Class::Monkey::Foo->someMethod('these', 'are', 'my', 'args);
441              
442             =cut
443              
444             sub original {
445 0     0 1   my ($class, $method, @args) = @_;
446 0 0         if (exists $Class::Monkey::Subs->{"$class\::$method"}) {
447 0           $Class::Monkey::Subs->{"$class\::$method"}->(@args);
448             }
449             else {
450 0           warn "Could not run original method '$method' in class $class. Not found";
451 0           return 0;
452             }
453             }
454              
455             =head2 override
456              
457             Overrides an already existing method. If the target method doesn't exist then Class::Monkey will throw an error.
458              
459             override 'foo' => sub {
460             return "foo bar";
461             },
462             qw;
463              
464             =cut
465              
466             sub override {
467 0     0 1   my ($method, $code, $class) = @_;
468              
469 0           _check_init($class);
470              
471 0 0         _doh "You need to specify a class to which your overridden method exists"
472             if ! $class;
473              
474 0 0         _doh "Method $method does not exist in $class. Perhaps you meant 'method' instead of 'override'?"
475             if ! $class->can($method);
476              
477 0           _add_to_subs("$class\::$method");
478 1     1   7 no strict 'refs';
  1         2  
  1         298  
479 0     0     *$method = sub { $code->(@_) };
  0            
480 0 0         if (ref($class)) {
481 0           $Class::Monkey::Iter++;
482 0           my $package = ref($class) . '::Class::Monkey::' . $Class::Monkey::Iter;
483 0           @{$package . '::ISA'} = (ref($class));
  0            
484 0           *{"${package}::${method}"} = \*$method;
  0            
485 0           bless $_[2], $package;
486             }
487             else {
488 0           *{$class . "::$method"} = \*$method;
  0            
489             }
490             }
491              
492             =head2 method
493              
494             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.
495              
496             method 'active_customers' => sub {
497             my $self = shift;
498             return $self->search({ status => 'active' });
499             },
500             qw;
501              
502             =cut
503              
504             sub method {
505 0     0 1   my ($method, $code, $class) = @_;
506            
507 0           _check_init($class);
508 0 0         _doh "You need to specify a class to which your created method will be initialised"
509             if ! $class;
510            
511 0 0         _doh "The method '$method' already exists in $class. Did you want to 'override' it instead?"
512             if $class->can($method);
513              
514 0           _add_to_subs("$class\::$method");
515 1     1   6 no strict 'refs';
  1         2  
  1         210  
516 0     0     *$method = sub { $code->(@_); };
  0            
517              
518 0           *{$class . "::$method"} = \*$method;
  0            
519             }
520              
521             =head2 before
522              
523             Simply adds code to the target method before the original code is ran
524              
525             # Foo.pm
526             package Foo;
527            
528             sub new { return bless {}, __PACKAGE__; }
529             sub hello { print "Hello, $self->{name}; }
530             1;
531              
532             # test.pl
533             use Class::Monkey qw;
534            
535             my $foo = Foo->new;
536             before 'hello' => {
537             my $self = shift;
538             $self->{name} = 'World';
539             },
540             qw;
541              
542             print $foo->hello . "\n";
543              
544             =cut
545              
546             sub before {
547 0     0 1   my ($method, $code, $class) = @_;
548 0           my $full;
549 0           _check_init($class);
550 0 0         $full = ref($class) ? ref($class) . "::${method}" : "${class}::${method}";
551 0           my $new_code;
552             my $old_code;
553 0 0         die "Could not find $method in the hierarchy for $class\n"
554             if ! $class->can($method);
555            
556 1     1   7 no strict 'refs';
  1         2  
  1         383  
557              
558 0           _add_to_subs($full);
559 0           $old_code = \&{$full};
  0            
560 0 0         if (ref($class)) {
561 0           $Class::Monkey::Iter++;
562 0           my $package = ref($class) . '::Class::Monkey::' . $Class::Monkey::Iter;
563 0           @{$package . '::ISA'} = (ref($class));
  0            
564 0           $full = "${package}::${method}";
565              
566             *$method = sub {
567 0     0     $code->(@_);
568 0           $old_code->(@_);
569 0           };
570            
571 0           *{$full} = \*$method;
  0            
572 0           bless $_[2], $package;
573             }
574             else {
575             *$method = sub {
576 0     0     $code->(@_);
577 0           $old_code->(@_);
578 0           };
579 0           *{$full} = \*$method;
  0            
580             }
581            
582            
583             }
584              
585             =head2 after
586              
587             Basically the same as C, but appends the code specified to the END of the original
588              
589             =cut
590              
591             sub after {
592 0     0 1   my ($method, $code, $class) = @_;
593              
594 0           _check_init($class);
595 0 0         my $full = ref($class) ? ref($class) . "::${method}" : "${class}::${method}";
596 0           my $new_code;
597             my $old_code;
598 0 0         die "Could not find $method in the hierarchy for $class\n"
599             if ! $class->can($method);
600              
601 0           $old_code = \&{$full};
  0            
602 1     1   22 no strict 'refs';
  1         2  
  1         452  
603 0           _add_to_subs($full);
604 0 0         if (ref($class)) {
605 0           $Class::Monkey::Iter++;
606 0           my $package = ref($class) . '::Class::Monkey::' . $Class::Monkey::Iter;
607 0           @{$package . '::ISA'} = (ref($class));
  0            
608 0           $full = "${package}::${method}";
609              
610             *$method = sub {
611 0     0     $old_code->(@_);
612 0           $code->(@_);
613 0           };
614              
615 0           *{$full} = \*$method;
  0            
616 0           bless $_[2], $package;
617             }
618             else {
619             *$method = sub {
620 0     0     $old_code->(@_);
621 0           $code->(@_);
622 0           };
623              
624 0           *{$full} = \*$method;
  0            
625             }
626             }
627              
628             =head2 around
629              
630             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 C<$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.
631              
632             package MyFoo;
633              
634             sub greet {
635             my ($self, $name) = @_;
636              
637             print "Hello, $name!\n";
638             }
639              
640             1;
641              
642             # test.pl
643              
644             use Class::Monkey qw;
645              
646             # only call greet if any arguments were passed to MyFoo->greet()
647             around 'greet' => sub {
648             my $method = shift;
649             my $self = shift;
650              
651             $self->$method(@_)
652             if @_;
653             },
654             qw;
655              
656             =cut
657              
658             sub around {
659 0     0 1   my ($method, $code, $class) = @_;
660              
661 0           my $full = "$class\::$method";
662 0 0         die "Could not find $method in the hierarchy for $class\n"
663             if ! $class->can($method);
664              
665 0           my $old_code = \&{$full};
  0            
666 1     1   8 no strict 'refs';
  1         2  
  1         322  
667             *$method = sub {
668 0     0     $code->($old_code, @_);
669 0           };
670              
671 0           _add_to_subs($full);
672 0 0         if (ref($class)) {
673 0           $Class::Monkey::Iter++;
674 0           my $package = ref($class) . '::Class::Monkey::' . $Class::Monkey::Iter;
675 0           @{$package . '::ISA'} = (ref($class));
  0            
676 0           *{"${package}::${method}"} = \*$method;
  0            
677 0           bless $_[2], $package;
678             }
679             else {
680 0           *{$full} = \*$method;
  0            
681             }
682             }
683              
684             =head2 unpatch
685              
686             Undoes any modifications made to patched methods, restoring it to its original state.
687              
688             override 'this' => sub {
689             print "Blah\n";
690             }, qw;
691            
692             unpatch 'this', 'FooClass';
693              
694             =cut
695              
696             sub unpatch {
697 0     0 1   my ($method, $class) = @_;
698              
699 0           my $sub = "$class\::$method";
700              
701 0 0         if (! exists $Class::Monkey::Subs->{$sub}) {
702 0           warn "Could not restore $method in $class because I have no recollection of it";
703 0           return 0;
704             }
705              
706 1     1   7 no strict 'refs';
  1         1  
  1         78  
707 0           *{$sub} = $Class::Monkey::Subs->{$sub};
  0            
708             }
709              
710             =head1 AUTHOR
711              
712             Brad Haywood
713              
714             =head1 LICENSE
715              
716             You may distribute this code under the same terms as Perl itself.
717              
718             =cut
719              
720             1;