File Coverage

blib/lib/Sub/Monkey.pm
Criterion Covered Total %
statement 48 176 27.2
branch 2 36 5.5
condition 0 2 0.0
subroutine 15 34 44.1
pod 8 9 88.8
total 73 257 28.4


line stmt bran cond sub pod time code
1             package Sub::Monkey;
2              
3 1     1   23261 use strict;
  1         2  
  1         34  
4 1     1   5 use warnings;
  1         2  
  1         295  
5              
6             our $VERSION = '0.004';
7             $Sub::Monkey::Subs = {};
8             $Sub::Monkey::CanPatch = [];
9             $Sub::Monkey::Classes = [];
10             $Sub::Monkey::Iter = 0;
11              
12             =head1 NAME
13              
14             Sub::Monkey - Dynamically and neatly monkey patch a module
15              
16             =head1 DEPRECATED
17              
18             Please note this module will not be maintained anymore. Instead, please check out C. It's the same project with updated pod and newed methods. Not to mention a namespace that makes a bit more sense..
19             Code you've made with Sub::Monkey will not break, just change the file where it's used as such: C and you're sorted.
20              
21             =head1 DESCRIPTION
22              
23             In some cases, rare cases, you may need to temporarily patch a module on-the-go. Sub::Monkey can help you achieve this by providing a set of methods to create, override and add hook modifiers, similar to M, but can apply them to remote modules (Not the current one).
24             This type of monkey patching is reasonably safe because you can plainly see what changes are being made to what modules. Obviously monkey patching isn't always the best alternative, but sometimes you may have no other choice.
25             Sub::Monkey also provides the ability to undo any patching you made with C.
26              
27             =head1 SYNOPSIS
28              
29             use Sub::Monkey qw;
30              
31             method 'needThisMethod' => sub {
32             ...
33             },
34             qw;
35              
36             We just created a brand new method in the Some::Package class. If you attempt to override an existing method using C, then Sub::Monkey will raise an error, because really you should be using C instead.
37             Remember, to patch a module with Sub::Monkey, you need to explicitly tell it you want to modify a class by importing it when you C. To do this for multiple modules just add them all into an array.
38              
39             use Sub::Monkey qw;
40              
41             =head1 METHODS
42              
43             =cut
44              
45             sub import {
46 1     1   8 my ($class, @args) = @_;
47 1         2 my $pkg = scalar caller;
48 1 50       5 if (scalar @args > 0) {
49 0         0 for my $m (@args) {
50 0         0 push @{$Sub::Monkey::CanPatch}, $m;
  0         0  
51             }
52 0         0 _extend_class(\@args, $pkg);
53             }
54              
55             _import_def(
56 1         4 $pkg,
57             undef,
58             qw/
59             override
60             method
61             before
62             after
63             around
64             unpatch
65             instance
66             original
67             /
68             );
69             }
70              
71             sub _extend_class {
72 0     0   0 my ($mothers, $class) = @_;
73              
74 0         0 foreach my $mother (@$mothers) {
75             # if class is unknown to us, import it (FIXME)
76 0 0       0 unless (grep { $_ eq $mother } @$Sub::Monkey::Classes) {
  0         0  
77 0         0 eval "use $mother";
78 0 0       0 warn "Could not load $mother: $@"
79             if $@;
80              
81 0         0 $mother->import;
82             }
83 0         0 push @$Sub::Monkey::Classes, $class;
84             }
85              
86             {
87 1     1   6 no strict 'refs';
  1         7  
  1         116  
  0         0  
88 0         0 @{"${class}::ISA"} = @$mothers;
  0         0  
89             }
90             }
91              
92             sub _import_def {
93 1     1   3 my ($pkg, $from, @subs) = @_;
94 1     1   7 no strict 'refs';
  1         2  
  1         318  
95 1 50       2 if ($from) {
96 0         0 for (@subs) {
97 0         0 *{$pkg . "::$_"} = \&{"$from\::$_"};
  0         0  
  0         0  
98             }
99             }
100             else {
101 1         2 for (@subs) {
102 8         10 *{$pkg . "::$_"} = \&$_;
  8         37  
103             }
104             }
105             }
106              
107             sub _doh {
108 0     0     my $err = shift;
109 0           die $err . "\n";
110             }
111              
112             sub _check_init {
113 0     0     my $class = shift;
114              
115 0 0         _doh "No class was specified" if ! $class;
116              
117 0           _doh "Not allowed to patch $class"
118 0 0         if ! grep { $_ eq $class } @{$Sub::Monkey::CanPatch};
  0            
119             }
120              
121             sub _add_to_subs {
122 0     0     my $sub = shift;
123 0 0         if (! exists $Sub::Monkey::Subs->{$sub}) {
124 0           $Sub::Monkey::Subs->{$sub} = {};
125 0           $Sub::Monkey::Subs->{$sub} = \&{$sub};
  0            
126 1     1   6 no strict 'refs';
  1         1  
  1         162  
127 0           *{__PACKAGE__ . "::$sub"} = \&{$sub};
  0            
  0            
128             }
129             }
130              
131             sub getscope {
132 0     0 0   my $self = shift;
133 0   0       my $pkg = $self||scalar caller;
134 0           return $pkg;
135             }
136             # modifiers
137              
138             =head2 instance
139              
140             Patch an instance method instead of an entire class
141              
142             # Pig.pm
143             package Pig;
144             sub new { return bless {}, shift; }
145             sub says { print "Oink!\n"; }
146              
147             # test.pl
148             package main;
149             use Sub::Monkey qw;
150              
151             my $pig = Pig->new;
152             my $pig2 = Pig->new;
153             instance 'says' => sub {
154             print "Meow\n";
155             },
156             $pig2;
157              
158             # only $pig2 will have its says method overridden
159              
160             =cut
161              
162             sub instance {
163 0     0 1   my($method, $code, $instance) = @_;
164 0           $Sub::Monkey::Iter++;
165 0           my $package = ref($instance) . '::Sub::Monkey' . $Sub::Monkey::Iter;
166 1     1   5 no strict 'refs';
  1         2  
  1         286  
167 0           @{$package . '::ISA'} = (ref($instance));
  0            
168 0           *{$package . '::' . $method} = $code;
  0            
169 0           bless $_[2], $package;
170             }
171              
172             =head2 original
173              
174             If you want to run the original version of a patched method, but not unpatch it right away
175             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.
176              
177             after 'someMethod' => sub {
178             print "Blah\n"
179             },
180             qw;
181              
182             original('Foo', 'someMethod', qw);
183              
184             OR if you prefer, you can just call C(@args)>
185              
186             Sub::Monkey::Foo->someMethod('these', 'are', 'my', 'args);
187              
188             =cut
189              
190             sub original {
191 0     0 1   my ($class, $method, @args) = @_;
192 0 0         if (exists $Sub::Monkey::Subs->{"$class\::$method"}) {
193 0           $Sub::Monkey::Subs->{"$class\::$method"}->(@args);
194             }
195             else {
196 0           warn "Could not run original method '$method' in class $class. Not found";
197 0           return 0;
198             }
199             }
200              
201             =head2 override
202              
203             Overrides an already existing method. If the target method doesn't exist then Sub::Monkey will throw an error.
204              
205             override 'foo' => sub {
206             return "foo bar";
207             },
208             qw;
209              
210             =cut
211              
212             sub override {
213 0     0 1   my ($method, $code, $class) = @_;
214              
215 0           _check_init($class);
216              
217 0 0         _doh "You need to specify a class to which your overridden method exists"
218             if ! $class;
219              
220 0 0         _doh "Method $method does not exist in $class. Perhaps you meant 'method' instead of 'override'?"
221             if ! $class->can($method);
222              
223 0           _add_to_subs("$class\::$method");
224 1     1   6 no strict 'refs';
  1         2  
  1         184  
225 0     0     *$method = sub { $code->(@_) };
  0            
226 0           *{$class . "::$method"} = \*$method;
  0            
227             }
228              
229             =head2 method
230              
231             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.
232              
233             method 'active_customers' => sub {
234             my $self = shift;
235             return $self->search({ status => 'active' });
236             },
237             qw;
238              
239             =cut
240              
241             sub method {
242 0     0 1   my ($method, $code, $class) = @_;
243            
244 0           _check_init($class);
245 0 0         _doh "You need to specify a class to which your created method will be initialised"
246             if ! $class;
247            
248 0 0         _doh "The method '$method' already exists in $class. Did you want to 'override' it instead?"
249             if $class->can($method);
250              
251 0           _add_to_subs("$class\::$method");
252 1     1   5 no strict 'refs';
  1         3  
  1         211  
253 0     0     *$method = sub { $code->(@_); };
  0            
254              
255 0           *{$class . "::$method"} = \*$method;
  0            
256             }
257              
258             =head2 before
259              
260             Simply adds code to the target method before the original code is ran
261              
262             # Foo.pm
263             package Foo;
264            
265             sub new { return bless {}, __PACKAGE__; }
266             sub hello { print "Hello, $self->{name}; }
267             1;
268              
269             # test.pl
270             use Sub::Monkey qw;
271            
272             my $foo = Foo->new;
273             before 'hello' => {
274             my $self = shift;
275             $self->{name} = 'World';
276             },
277             qw;
278              
279             print $foo->hello . "\n";
280              
281             =cut
282              
283             sub before {
284 0     0 1   my ($method, $code, $class) = @_;
285            
286 0           _check_init($class);
287 0           my $full;
288 0 0         if (ref($method) eq 'ARRAY') {
289 0           for my $subname (@$method) {
290 0           $full = "$class\::$subname";
291 0           my $alter_sub;
292             my $new_code;
293 0           my $old_code;
294 0 0         die "Could not find $subname in the hierarchy for $class\n"
295             if ! $class->can($subname);
296              
297 0           $old_code = \&{$full};
  0            
298 1     1   5 no strict 'refs';
  1         2  
  1         153  
299             *$subname = sub {
300 0     0     $code->(@_);
301 0           $old_code->(@_);
302 0           };
303              
304 0           _add_to_subs($full);
305 0           *{$full} = \*$subname;
  0            
306             }
307             }
308             else {
309 0           $full = "$class\::$method";
310 0           my $alter_sub;
311             my $new_code;
312 0           my $old_code;
313 0 0         die "Could not find $method in the hierarchy for $class\n"
314             if ! $class->can($method);
315              
316 0           $old_code = \&{$full};
  0            
317 1     1   5 no strict 'refs';
  1         2  
  1         201  
318             *$method = sub {
319 0     0     $code->(@_);
320 0           $old_code->(@_);
321 0           };
322              
323 0           _add_to_subs($full);
324 0           *{$full} = \*$method;
  0            
325             }
326             }
327              
328             =head2 after
329              
330             Basically the same as C, but appends the code specified to the END of the original
331              
332             =cut
333              
334             sub after {
335 0     0 1   my ($method, $code, $class) = @_;
336              
337 0           _check_init($class);
338 0           my $full = "$class\::$method";
339 0           my $alter_sub;
340             my $new_code;
341 0           my $old_code;
342 0 0         die "Could not find $method in the hierarchy for $class\n"
343             if ! $class->can($method);
344              
345 0           $old_code = \&{$full};
  0            
346 1     1   6 no strict 'refs';
  1         1  
  1         176  
347             *$method = sub {
348 0     0     $old_code->(@_);
349 0           $code->(@_);
350 0           };
351              
352 0           _add_to_subs($full);
353 0           *{$full} = \*$method;
  0            
354             }
355              
356             =head2 around
357              
358             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.
359              
360             package MyFoo;
361              
362             sub greet {
363             my ($self, $name) = @_;
364              
365             print "Hello, $name!\n";
366             }
367              
368             1;
369              
370             # test.pl
371              
372             use Sub::Monkey qw;
373              
374             # only call greet if any arguments were passed to MyFoo->greet()
375             around 'greet' => sub {
376             my $method = shift;
377             my $self = shift;
378              
379             $self->$method(@_)
380             if @_;
381             },
382             qw;
383              
384             =cut
385              
386             sub around {
387 0     0 1   my ($method, $code, $class) = @_;
388              
389 0           my $full = "$class\::$method";
390 0 0         die "Could not find $method in the hierarchy for $class\n"
391             if ! $class->can($method);
392              
393 0           my $old_code = \&{$full};
  0            
394 1     1   6 no strict 'refs';
  1         2  
  1         206  
395             *$method = sub {
396 0     0     $code->($old_code, @_);
397 0           };
398              
399 0           _add_to_subs($full);
400 0           *{$full} = \*$method;
  0            
401             }
402              
403             =head2 unpatch
404              
405             Undoes any modifications made to patched methods, restoring it to its original state.
406              
407             override 'this' => sub { print "Blah\n"; }, qw;
408            
409             unpatch 'this' => 'FooClass';
410              
411             =cut
412              
413             sub unpatch {
414 0     0 1   my ($method, $class) = @_;
415              
416 0           my $sub = "$class\::$method";
417              
418 0 0         if (! exists $Sub::Monkey::Subs->{$sub}) {
419 0           warn "Could not restore $method in $class because I have no recollection of it";
420 0           return 0;
421             }
422              
423 1     1   6 no strict 'refs';
  1         2  
  1         58  
424 0           *{$sub} = $Sub::Monkey::Subs->{$sub};
  0            
425             }
426              
427             =head1 AUTHOR
428              
429             Brad Haywood
430              
431             =head1 LICENSE
432              
433             You may distribute this code under the same terms as Perl itself.
434              
435             =cut
436              
437             1;