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