1=head1 NAME 2 3WWW::Mechanize::Plugin::Cookbook - how to write plugins for WWW::Mechanize::Pluggable 4 5=head1 DESCRIPTION 6 7This document describes what a C<WWW::Mechanize::Pluggable> plugin is, 8how they work in connection with the base module, and gives examples 9of how one would design a new plugin. 10 11=over 4 12 13This cookbook addresses the current state of the C<Pluggable> interface; 14future versions are expected to greatly streamline the process of creating 15plugins and hooks. 16 17=back 18 19=head1 PLUGIN BASICS 20 21A plugin is basically as specially-named package that is automatically loaded 22by a parent class. This document outlines the interface between 23C<WWW::Mechanize::Pluggable> and its plugin classes. 24 25=head2 Flow of Control 26 27When C<WWW::Mechanize::Pluggable> is loaded, it searches C<@INC> for 28modules whose names begin with C<WWW::Mechanize::Plugin> and calls 29C<import> for the package, using the arguments supplied on 30C<WWW::Mechanize::Pluggable>'s own C<use> line. This allows you to 31parameterize the plugins if you wish. 32 33When a C<WWW::Mechanize::Pluggable> object is instantiated, its 34C<new> method calls each of the plugins' C<init> method. 35Typically, C<init()> exports methods back into 36the caller's namespace, and also calls C<pre_hook> and C<post_hook> 37to wrap any of C<WWW::Mechanize>'s methods it desires. 38 39When a C<WWW::Mechanize> method is called, C<WWW::Mechanize::Pluggable>'s 40C<AUTOLOAD> takes control. It calls any pre-hooks that have been installed 41for the method; if any of them return a true value, the actual method 42call is skipped. C<WW::Mechanize::Pluggable> then calls the method 43(if it should) using the same context in which the method was originally 44callled, saving the return value. The post-hooks are then called, and the 45return value from the method is returned to the original caller. 46 47=head2 What you can do 48 49Essentially, you now have complete control over what any method in the base 50class does. You can 51 52=over 4 53 54=item * alter the parameter list 55 56=item * process the call yourself 57 58=item * conditionally get involved, or not 59 60=item * post-process the results after the call 61 62=back 63 64=head1 API TO WWW::MECHANIZE::PLUGGABLE 65 66=head2 import 67 68Called as C<import($class, %args)>. 69 70This routine is optional; it is called when your plugin is loaded 71by C<WWW::Mechanize::Pluggable>. You can use this to parameterize 72your plugin via arguments on the C<use> statement. 73 74It's recommended that you supply arguments as key-value pairs; 75this will make it possible for C<WWW::Mechanize::Pluggable> 76to remove the "used-up" parameters from the c<use> line by 77returning the keys you want to have removed. 78 79Here's a sample C<import> method: 80 81 sub import { 82 my($class, %args) = @_; 83 if defined(my $value = $args{'mine'}) { 84 if (_is_appropriate($value)) { 85 # do whatever ,,, 86 } 87 } 88 return ("mine"); 89 } 90 91This looks for the C<mine> parameter on the C<use>. 92It processes it as appropriate and returns the 93key so that C<WWW::Mechanize::Pluggable> will delete it. 94 95=head2 init 96 97Called as C<init($pluggable)>. 98 99The C<init> method allows your plugin a chance to export 100subroutines and store information appropriate for its 101proper functioning in the parent C<WWW::Mechanize::Pluggable> 102object. It also can be used to set up pre-hooks and 103post-hooks for methods. 104 105=over 4 106 107Note that at present it isn't possible to add hooks for 108methods installed by other plugins; a future release of 109this software may be able to do this. 110 111=back 112 113Because other plugins will be doing the same thing, it's 114important to choose unique method names and field names. 115It's proabably a good idea to prefix field names with the 116name of your plugin, like C<_MyPlugin_data>. 117 118It's possible that we may change the interface in a future 119release of C<WWW::Mechanize::Pluggable> to support 120"inside-out" objects (see http://www.windley.com/archives/2005/08/best_practices.shtml 121for an example). 122 123Sample init function: 124 125 sub init { 126 my($parent_object, %args) = @_; 127 $parent_object->{_myplugin_foo} = "my data"; 128 *{caller() . '::myplugin_method'} = \&my_implementation; 129 $parent_object->pre_hook('get', sub { &my_prehook(@_) } ); 130 $parent_object->post_hook('get', sub { &my_prehook(@_) } ); 131 my @removed; 132 if ($args{'my_arg'}) { 133 # process my_arg 134 push @removes, 'my_arg'; 135 } 136 @removed; 137 } 138 139The anonymous subroutine wrapping the hook setup currently is 140necessary to prevent the hook from being called during its 141installation; this needs to be fixed. The anonymous subroutine 142works for the moment, and will work in future releases, so 143go head and use it for now. 144 145Also note that we have the same kind of interface that we do 146in C<import>; you can parameterize a particular plugin by 147putting the parameters (key=>value-style) on the C<new> 148and then processing them in C<init>, and deleting them 149after processing by returning the list of names. 150 151=head2 pre_hook 152 153Called as C<$parent_object->pre_hook('method_name", $subref)>. 154 155Installs the referenced subroutine as a pre-hook for the 156named method. Currently, only C<WWW::Mechanize> methods can 157be hooked; future releases may allow methods supplied by plugins 158to be hooked as well. 159 160=head2 post_hook 161 162Called as C<$parent_object->pre_hook('method_name", $subref)>. 163 164Installs the referenced subroutine as a post-hook for the 165named method. Currently, only C<WWW::Mechanize> methods can 166be hooked; future releases may allow methods supplied by plugins 167to be hooked as well. 168 169=head1 YOUR CODE 170 171Since pre-hooks and post-hooks are all about getting your 172code involved in things, this section details how all that 173works. 174 175=head2 Prehooks and posthooks 176 177Called as C<your_hook($pluggable, $internal_mech, @args)>. 178 179This is the subroutine that you passed a reference to in 180the call to either C<pre_hook> or C<post_hook>. It can do 181anything you like; it has access to both the 182\C<WWW::Mechanize::Pluggable> object and to the 183internal C<WWW::Mechanize> object, as well as to the 184parameters with which the method was called. 185 186If your code is a pre-hook, it can cause 187C<WWW::Mechanize::Pluggable> to skip the method 188call altogether by returning a true value. 189 190Sample pre-hook: 191 192 sub my_prehook { 193 my($pluggable, $mech, @args) = @_; 194 195 # We'll assume that this is a hook for 'get'. 196 if ($args[0] =~ /$selected_url/) { 197 198 # alter the URL to what we want 199 $args[0] =~ s/$what_we_dont_want/$what_we_do/; 200 } 201 # force another try with the altered URL. 202 $pluggable->get(@args); 203 204 # don't actually do the get with the old URL. 205 return 'skip'; 206 } 207 208We used this approach because the interface currently doesn't 209allow us to alter the parameter list; this is something we 210probably should do in the next release. 211 212=head1 RECIPIES 213 214=head2 Adding a new acessor 215 216To avoid doing a lot extra monkey coding, C<Class::Accessor::Fast> is highly recommended. 217 218 package WWW::Mechanize::Plugin::MyPlugin; 219 use base qw(Class::Accessor::Fast); 220 __PACKAGE__->mk_accessors(qw(foo bar baz)); 221 222You can now use the newly-created accessors any way you like; often 223you'll use them to store data for other methods that are exported to 224C<WWW::Mechanize::Pluggable>. 225 226=head2 Adding a new method 227 228This is done (for the moment) by using a construct like this: 229 230 *{caller() . '::new_method'} = \&localsub; 231 232This would call any subroutine or method call to new_method 233via the Mech::Pluggable object to be dispatched to localsub 234in this package. 235 236=head2 Replacing a method 237 238In init, install a pre_hook for the method which does 239something like this: 240 241 sub init { 242 pre_hook('desired_method', sub { \&substitute(@_) }); 243 244 sub substitute { 245 my($pluggable, $mech, @_) = @_; 246 # Do whatever you want; 247 return "skip"; 248 } 249 250Note the anonymous sub construct in the call to pre_hook. 251This is necessary because the construct C<\&substitute> 252tries to call substitute() immediately, which we do 253I<not> want. 254 255We return "skip" as a mnemonic that a true value causes 256the real call to be skipped. 257 258=head2 Retrying a method call 259 260This is done with a prehook to count how many times 261we've tried to retry an action, and a posthook to 262 263=over 4 264 265=item * take whatever action is needed to set up for the retry 266 267=item * call back() on the Mech object 268 269=item * repeat the last action again on the Mech object 270 271=item * up the count of tries 272 273=back 274 275The prehook is needed to keep the retry from going into 276an infinite loop. 277 278=head1 CREDITS 279 280The Perl Advent Calendar (http://www.perladvent.org/2004/6th/) for bringing Module::Pluggable to my attention. 281 282Damian Conway, for showing us how to do things that Just Work. 283 284Andy Lester, for WWW::Mechanize. 285 286=cut 287 288use strict; # or get dinged by CPANTS 289 290"It's only documentation but I like it"; 291