File Coverage

blib/lib/Class/CompoundMethods.pm
Criterion Covered Total %
statement 58 62 93.5
branch 15 20 75.0
condition 6 8 75.0
subroutine 12 12 100.0
pod 2 2 100.0
total 93 104 89.4


line stmt bran cond sub pod time code
1             package Class::CompoundMethods;
2              
3 3     3   113309 use strict;
  3         7  
  3         120  
4 3     3   16 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS %METHODS);
  3         5  
  3         207  
5              
6 3     3   18 use Exporter ();
  3         9  
  3         229  
7             *import = \&Exporter::import;
8             @EXPORT_OK = qw(append_method prepend_method );
9             %EXPORT_TAGS = ( all => \@EXPORT_OK );
10              
11 3     3   37 use B qw( svref_2object );
  3         5  
  3         1458  
12              
13             # use Smart::Comments;
14              
15             =pod
16              
17             =head1 NAME
18              
19             Class::CompoundMethods - Create methods from components
20              
21             =head1 VERSION
22              
23             0.05
24              
25             =cut
26              
27             $VERSION = '0.05';
28              
29             =pod
30              
31             =head1 SYNOPSIS
32              
33             package Object;
34             use Class::CompoundMethods 'append_method';
35              
36             # This installs both versioning_hook and auditing_hook into the
37             # method Object::pre_insert.
38             append_method( pre_insert => "versioning_hook" );
39             append_method( pre_insert => "auditing_hook" );
40              
41             =head1 DESCRIPTION
42              
43             This allows you to install more than one method into a single method
44             name. I created this so I could install both versioning and auditing
45             hooks into another module's object space. So instead of creating a
46             single larger method which incorporates the functionality of both
47             hooks I created C/C to install a
48             wrapper method as needed.
49              
50             If only one method is ever installed into a space, it is installed
51             directly with no wrapper. Once there are two or more components, a
52             hook method is installed which will call each component in order.
53              
54             =head1 PUBLIC METHODS
55              
56             =over 4
57              
58             =item append_method( $method_name, $method )
59              
60             append_method( $method_name, $method );
61              
62             This function takes two parameters - a method name and the method to install.
63              
64             C<$method_name> may be fully qualified. If not, Class::CompoundMethods
65             looks for your method in your current package.
66              
67             append_method( 'Object::something', ... );
68             append_method( 'something', ... );
69              
70             C<$method> may be either a code reference or a method name. It may be
71             fully qualified.
72              
73             append_method( ..., sub { ... } );
74             append_method( ..., \ &some_hook );
75             append_method( ..., 'Object::some_hook' );
76             append_method( ..., 'some_hook' );
77              
78             =cut
79              
80             sub append_method {
81              
82             # This takes a method and adds it onto the end of all previous methods
83 4     4 1 1341 my ( $method_name, $method_to_install ) = @_;
84              
85 4         22 return _x_method(
86             { method_name => $method_name,
87             method_to_install => $method_to_install,
88             add_method => \&_append_method,
89             existing_method => \&_append_method,
90             }
91             );
92             }
93              
94             =pod
95              
96              
97             =item prepend_method( $method_name, $method )
98              
99             prepend_method( $method_name, $method );
100              
101             This function takes two parameters - a method name and the method to install.
102              
103             C<$method_name> may be fully qualified. If not, Class::CompoundMethods
104             looks for your method in your current package.
105              
106             prepend_method( 'Object::something', ... );
107             prepend_method( 'something', ... );
108              
109             C<$method> may be either a code reference or a method name. It may be
110             fully qualified.
111              
112             prepend_method( ..., sub { ... } );
113             prepend_method( ..., \ &some_hook );
114             prepend_method( ..., 'Object::some_hook' );
115             prepend_method( ..., 'some_hook' );
116              
117             =cut
118              
119             sub prepend_method {
120              
121             # This takes a method and inserts before all other methods into the method
122             # slot.
123 4     4 1 1645 my ( $method_name, $method_to_install ) = @_;
124              
125 4         27 return _x_method(
126             { method_name => $method_name,
127             method_to_install => $method_to_install,
128             add_method => \&_prepend_method,
129             existing_method => \&_prepend_method,
130             }
131             );
132             }
133              
134             # =pod
135             #
136             # =item method_list( $method_name )
137             #
138             # =cut
139             #
140             # sub method_list {
141             #
142             # # Modifying the $METHODS{...} array only works when the stub function is
143             # # installed into the method slot. I haven't documented this
144             # # function and you shouldn't be using it ... unless you modify
145             # # ->_x_method to always install the stub method in which case it
146             # # becomes safe to count on ->method_list.
147             # my ($method_name) = @_;
148             #
149             # return $METHODS{$method_name} || [];
150             # }
151              
152             =back
153              
154             =head2 EXAMPLES
155              
156             =over 4
157              
158             =item Example 1
159              
160             use Class::CompoundMethods qw(append_method);
161              
162             # This installs both versioning_hook and auditing_hook into the
163             # method Object::pre_insert.
164             append_method( 'Object::something' => \ &versioning_hook );
165              
166             package Object;
167             prepend_method( 'something' => \ &auditing_hook );
168              
169             =item Example 2
170              
171             package GreenPartyDB::Database;
172             use Class::CompoundMethods qw(append_method);
173              
174             my @versioned_tables = ( ... );
175             my @audited_tables = ( ... );
176            
177             for my $table ( @versioned_tables ) {
178             my $package = __PACKAGE__ . "::" . $table;
179             append_method( $package . "::pre_insert", \ &versioning_hook );
180             append_method( $package . "::pre_update", \ &versioning_hook );
181             append_method( $package . "::pre_delete", \ &versioning_hook );
182             }
183              
184             for my $table ( @audited_tables ) {
185             my $package = __PACKAGE__ . "::" . $table;
186             append_method( $package . "::pre_insert", \ &auditing_hook );
187             append_method( $package . "::pre_update", \ &auditing_hook );
188             append_method( $package . "::pre_delete", \ &auditing_hook );
189             }
190              
191             =back
192              
193             =head2 EXPORT
194              
195             This class optionally exports the C and
196             C functions. It also uses the ':all' tag.
197              
198             use Class::CompoundMethods qw( append_method );
199              
200             use Class::CompoundMethods qw( :all );
201              
202             =head1 COPYRIGHT & LICENSE
203              
204             Copyright (c) 2005 Joshua ben Jore All rights reserved. This program
205             is free software; you can redistribute it and/or modify it under the
206             same terms as Perl itself.
207              
208             =head1 AUTHOR
209              
210             "Joshua ben Jore"
211              
212             =head1 SEE ALSO
213              
214             RFC Class::AppendMethods L
215              
216             Installing chained methods L
217              
218             =cut
219              
220             ## PRIVATE FUNCTIONS
221              
222             sub _append_method {
223 5     5   7 my ($p) = @_;
224 5         6 push @{ $p->{stash} }, $p->{method};
  5         12  
225 5         10 return;
226             }
227              
228             sub _prepend_method {
229 5     5   7 my ($p) = @_;
230 5         7 unshift @{ $p->{stash} }, $p->{method};
  5         15  
231 5         8 return;
232             }
233              
234             sub _function_package {
235 16     16   23 my ($sub) = @_;
236              
237 16         21 return eval { svref_2object($sub)->STASH->NAME; };
  16         229  
238             }
239              
240             sub _x_method {
241              
242             # This is a general function used by ->prepend_method and
243             # ->append_method to alter a method slot. The four arguments are
244             # the method name to install, the slot to write to and two
245             # functions for managing the $METHODS{...} array.
246              
247             # method_name:
248             # This may be either a fully qualified or unqualified method name.
249             # eg: 'GreenPartyDB::Database::person::pre_insert'
250             # vs
251             # 'pre_insert' (and the calling method was done from within the
252             # 'GreenPartyDB::Database::person' namespace)
253             #
254             # Perhaps in the future it would be useful to also support methods in
255             # the form 'package->method' /^ ([^ -]*) \s* -> \s* ([\w+])/x .
256              
257             # method_to_install:
258             # This may be either an fully qualified/unqualified method name or a code
259             # reference.
260              
261             # add_method:
262              
263             # existing_method:
264 8     8   14 my ($p) = @_;
265 8         22 my ( $method_name, $method_to_install, $add_method, $existing_method )
266 8         17 = @{$p}
267             {qw(method_name method_to_install add_method existing_method )};
268              
269             # Get the package of the user of Class::CompoundMethods.
270 8         12 my ( $package, $filename, $line );
271             {
272              
273             # Look upwards in the call stack until I either run out of
274             # stack or find something that isn't from this package.
275 8         10 my $cx = 1;
  8         10  
276 8         30 ++$cx until __PACKAGE__ ne caller $cx;
277 8         37 ( $package, $filename, $line ) = caller $cx;
278              
279             ### Context: $cx
280             }
281              
282 3     3   15 no strict 'refs'; ## no critic
  3         6  
  3         1441  
283 8         29 local $^W;
284              
285             # If the method name isn't qualified then I assume it exists in the
286             # caller's package.
287 8 100       31 unless ( $method_name =~ /::/ ) {
288             ### Fixing up target $method_name from $package
289 2         7 $method_name = "${package}::$method_name";
290             }
291              
292             ### Target method name: $method_name
293              
294             # If I was given a method name then fetch the code
295             # reference from the named slot
296 8 100       29 unless ( ref $method_to_install ) {
297              
298             # If the method is not qualified with a package name then grab the
299             # method from the caller's own package.
300 6 100       21 unless ( $method_to_install =~ /::/ ) {
301             ### Fixing up source: $method_to_install, from: $package
302 2         5 $method_to_install = "${package}::$method_to_install";
303             }
304              
305             ### Source symref: $method_to_install
306 6 50       23 defined &$method_to_install
307             or die "Couldn't get $method_to_install in $filename at $line.\n";
308              
309 6         14 $method_to_install = \&$method_to_install;
310             }
311              
312             # Track the list of references to install
313 8   100     45 my $methods_to_call = $METHODS{$method_name} ||= [];
314              
315             # Protect against clobbering whatever was there previously. Its ok
316             # to clobber it if its just the hook method or if its already in
317             # the list of things C::CM knows to call as a component.
318 8 100 66     198 if ( defined &$method_name
  0   66     0  
319             and ( __PACKAGE__ ne _function_package( \&$method_name ) )
320             and not scalar grep { $_ == \&$method_name } @$methods_to_call )
321             {
322             ### Saving original method
323 2         23 $existing_method->(
324             { stash => $methods_to_call,
325             method => \&$method_name,
326             package => $package,
327             filename => $filename,
328             line => $line
329             }
330             );
331             }
332              
333             ### Saving original method
334 8         53 $add_method->(
335             { stash => $methods_to_call,
336             method => $method_to_install,
337             package => $package,
338             filename => $filename,
339             line => $line
340             }
341             );
342              
343             # Install the hook if there isn't one there aleady.
344 8 100       31 if ( __PACKAGE__ eq _function_package( \&$method_name ) ) {
    50          
    50          
345              
346             ### Ignoring pre-existing multi-method hook.
347             }
348             elsif ( 1 == @$methods_to_call ) {
349              
350             ### Installing the single method.
351 0         0 *$method_name = $methods_to_call->[0];
352             }
353             elsif ( 1 < @$methods_to_call ) {
354              
355             ### Installing the multi-method hook.
356             *$method_name = sub {
357 8     8   50 my ($self) = shift;
358              
359 8 50       26 if (wantarray) {
    50          
360 0         0 return map $self->$_(@_), @$methods_to_call;
361             }
362             elsif ( defined wantarray ) {
363 0         0 return join( ' ', map $_->$_(@_), @$methods_to_call );
364             }
365             else {
366 8         32 $self->$_(@_) for @$methods_to_call;
367 8         90 return;
368             }
369 2         20 };
370             }
371              
372             # Return the method as a convenience (for who knows what, I don't know)
373 8         14 return \&{$method_name};
  8         197  
374             }
375              
376             "Fine! Since you're too busy playing with people's minds, I'll just go off to the other room to play with myself!";