File Coverage

blib/lib/Params/Coerce.pm
Criterion Covered Total %
statement 84 93 90.3
branch 29 48 60.4
condition n/a
subroutine 22 22 100.0
pod 2 2 100.0
total 137 165 83.0


line stmt bran cond sub pod time code
1             package Params::Coerce;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Params::Coerce - Allows your classes to do coercion of parameters
8              
9             =head1 SYNOPSIS
10              
11             # Coerce a object of class Foo to a Bar
12             my $bar = Params::Coerce::coerce('Bar', $Foo)
13            
14             # Create a coercion param function
15             use Params::Coerce '_Bar' => 'Bar';
16             my $bar = _Bar($Foo);
17            
18             # Usage when Bar has a 'from' method
19             my $bar = Bar->from($Foo);
20              
21             Real world example using L.
22              
23             # My class needs a URI
24             package Web::Spider;
25            
26             use URI;
27             use Params::Coerce 'coerce';
28            
29             sub new {
30             my $class = shift;
31            
32             # Where do we start spidering
33             my $start = coerce('URI', shift) or die "Wasn't passed a URI";
34            
35             bless { root => $start }, $class;
36             }
37            
38             #############################################
39             # Now we can do the following
40            
41             # Pass a URI as normal
42             my $URI = URI->new('http://ali.as/');
43             my $Spider1 = Web::Spider->new( $URI );
44            
45             # We can also pass anything that can be coerced into being a URI
46             my $Website = HTML::Location->new( '/home/adam/public_html', 'http://ali.as' );
47             my $Spider2 = Web::Spider->new( $Website );
48              
49             =head1 DESCRIPTION
50              
51             A big part of good API design is that we should be able to be flexible in
52             the ways that we take parameters.
53              
54             Params::Coerce attempts to encourage this, by making it easier to take a
55             variety of different arguments, while adding negligable additional complexity
56             to your code.
57              
58             =head2 What is Coercion
59              
60             "Coercion" in computing terms generally referse to "implicit type
61             conversion". This is where data and object are converted from one type to
62             another behind the scenes, and you just just magically get what you need.
63              
64             The L pragma, and its string overloading is the form of coercion
65             you are most likely to have encountered in Perl programming. In this case,
66             your object is automatically (within perl itself) coerced into a string.
67              
68             C is intended for higher-order coercion between various
69             types of different objects, for use mainly in subroutine and (mostly)
70             method parameters, particularly on external APIs.
71              
72             =head2 __as_Another_Class Methods
73              
74             At the heart of C is the ability to transform objects from
75             one thing to another. This can be done by a variety of different
76             mechanisms.
77              
78             The prefered mechanism for this is by creating a specially named method
79             in a class that indicates it can be coerced into another type of object.
80              
81             As an example, L provides an object method that returns an
82             equivalent L object.
83              
84             # In the package HTML::Location
85            
86             # Coerce to a URI
87             sub __as_URI {
88             my $self = shift;
89             return URI->new( $self->uri );
90             }
91              
92             =head2 __from_Another_Class Methods
93              
94             From version 0.04 of C, you may now also provide
95             __from_Another_Class methods as well. In the above example, rather then
96             having to define a method in L, you may instead define
97             one in L. The following code has an identical effect.
98              
99             # In the package URI
100            
101             # Coerce from a HTML::Location
102             sub __from_HTML_Location {
103             my $Location = shift;
104             return URI->new( $Location->uri );
105             }
106              
107             C will only look for the __from method, if it does not
108             find a __as method.
109              
110             =head2 Loading Classes
111              
112             One thing to note with the C<__as_Another_Class> methods is that you are
113             B required to load the class you are converting to in the class you
114             are converting from.
115              
116             In the above example, L does B have to load the URI
117             class. The need to load the classes for every object we might some day need
118             to be coerced to would result in highly excessive resource usage.
119              
120             Instead, C guarentees that the class you are converting to
121             C be loaded before it calls the __as_Another_Class method. Of course,
122             in most situations you will have already loaded it for another purpose in
123             either the From or To classes and this won't be an issue.
124              
125             If you make use of some class B the class you are being coerced
126             to in the __as_Another_Class method, you will need to make sure that is loaded
127             in your code, but it is suggested that you do it at run-time with a
128             C if you are not using it already elsewhere.
129              
130             =head2 Coercing a Parameter
131              
132             The most explicit way of accessing the coercion functionality is with the
133             Params::Coerce::coerce function. It takes as its first argument the name
134             of the class you wish to coerce B, followed by the parameter to which you
135             wish to apply the coercion.
136              
137             package My::Class;
138            
139             use URI ();
140             use Params::Coerce '_URI' => 'URI';
141            
142             sub new {
143             my $class = shift;
144            
145             # Take a URI argument
146             my $URI = Params::Coerce::coerce('URI', shift) or return;
147            
148             ...
149             }
150              
151             For people doing procedural programming, you may also import this function.
152              
153             # Import the coerce function
154             use Params::Coerce 'coerce';
155              
156             Please note thatThe C function is the B function
157             that can be imported, and that the two argument pragma (or the passing of
158             two or more arguments to ->import) means something different entirely.
159              
160             =head2 Importing Parameter Coercion Methods
161              
162             The second way of using Params::Coerce, and the more common one for
163             Object-Oriented programming, is to create method specifically for taking
164             parameters in a coercing manner.
165              
166             package My::Class;
167            
168             use URI ();
169             use Params::Coerce '_URI' => 'URI';
170            
171             sub new {
172             my $class = shift;
173              
174             # Take a URI as parameter
175             my $URI1 = $class->_URI(shift) or return;
176             my $URI2 = _URI(shift) or return;
177             ...
178             }
179              
180             =head2 The C Constructor
181              
182             From version C<0.11> of C, an additional mechanism is
183             available with the importable C constructor.
184              
185             package My::Class;
186            
187             use Params::Coerce 'from';
188            
189             package Other::Class;
190            
191             sub method {
192             my $self = shift;
193             my $My = My::Class->from(shift) or die "Bad param";
194             ...
195             }
196              
197             This is mainly a convenience. The above is equivalent to
198              
199             package My::Class;
200            
201             use Params::Coerce 'from' => 'Params::Coerce';
202              
203             In future versions, this C<-Efrom> syntax may also tweak the resolution
204             order of the coercion.
205              
206             =head2 Chained Coercion
207              
208             While it is intended that Params::Coerce will eventually support coercion
209             using multiple steps, like C<__as_HTML_Location->__as_URI>>,
210             it is not currently capable of this. At this time only a single coercion
211             step is supported.
212              
213             =head1 FUNCTIONS
214              
215             =cut
216              
217 4     4   79493 use 5.005;
  4         16  
  4         156  
218 4     4   24 use strict;
  4         8  
  4         142  
219 4     4   41 use Carp ();
  4         8  
  4         57  
220 4     4   22 use Scalar::Util ();
  4         6  
  4         136  
221 4         398 use Params::Util '_IDENTIFIER',
222             '_INSTANCE',
223 4     4   4316 '_CLASS';
  4         23405  
224              
225             # Load Overhead: 52k
226              
227 4     4   33 use vars qw{$VERSION};
  4         8  
  4         198  
228             BEGIN {
229 4     4   345 $VERSION = '0.14';
230             }
231              
232             # The hint cache
233             my %hints = ();
234              
235              
236              
237              
238              
239             #####################################################################
240             # Use as a Pragma
241              
242             sub import {
243 9     9   63 my $class = shift;
244 9 100       1137 my @param = @_ or return;
245 5 50       16 Carp::croak("Too many parameters") if @param > 2; # Um, what?
246              
247             # We'll need to know who is calling us
248 5         12 my $pkg = caller();
249              
250             # We export them the coerce function if they want it
251 5 100       17 if ( @param == 1 ) {
252 3 100       14 if ( $param[0] eq 'coerce' ) {
    50          
253 4     4   28 no strict 'refs';
  4         8  
  4         267  
254 1         3 *{"${pkg}::coerce"} = *coerce;
  1         5  
255 1         88 return 1;
256             } elsif ( $param[0] eq 'from' ) {
257             # They want a from constructor
258 4     4   21 no strict 'refs';
  4         8  
  4         2590  
259 2         4 *{"${pkg}::from"} = *from;
  2         17  
260 2         201 return 1;
261             } else {
262 0         0 Carp::croak "Params::Coerce does not export '$_[0]'";
263             }
264             }
265              
266             # The two argument form is 'method' => 'class'
267             # Check the values given to us.
268 2 50       82 my $method = _IDENTIFIER($param[0]) or Carp::croak "Illegal method name '$param[0]'";
269 2 50       72 my $want = _CLASS($param[1]) or Carp::croak "Illegal class name '$param[1]'";
270 2 50       27 _function_exists($pkg, $method) and Carp::croak "Cannot create '${pkg}::$method'. It already exists";
271              
272             # Make sure the class is loaded
273 2 50       5 unless ( _loaded($want) ) {
274 0         0 eval "require $want";
275 0 0       0 croak($@) if $@;
276             }
277              
278             # Create the method in our caller
279 2     2   138 eval "package $pkg;\nsub $method {\n\tParams::Coerce::_coerce('$want', \$_[-1])\n}";
  2     2   3174  
  2         3327  
280 2 50       7 Carp::croak("Failed to create coercion method '$method' in $pkg': $@") if $@;
281              
282 2         2099 1;
283             }
284              
285             =pod
286              
287             =head2 coerce $class, $param
288              
289             The C function takes a class name and a single parameter and
290             attempts to coerce the parameter into the intended class, or one of its
291             subclasses.
292              
293             Please note that it is the responsibility of the consuming class to ensure
294             that the class you wish to coerce to is loaded. C will check this
295             and die is it is not loaded.
296              
297             Returns an instance of the class you specify, or one of its subclasses.
298             Returns C if the parameter cannot be coerced into the class you wish.
299              
300             =cut
301              
302             sub coerce($$) {
303             # Check what they want properly first
304 5 50   5 1 4680 my $want = _CLASS($_[0]) or Carp::croak("Illegal class name '$_[0]'");
305 5 50       54 _loaded($want) or Carp::croak("Tried to coerce to unloaded class '$want'");
306              
307             # Now call the real function
308 5         14 _coerce($want, $_[1]);
309             }
310              
311             # The from method that is imported into the classes
312             sub from {
313 4 50   4 1 10874 @_ == 2 or Carp::croak("'->from must be called as a method with a single param");
314 4         16 _coerce(@_);
315             }
316              
317             # Internal version with less checks. Should ONLY be called once
318             # the first argument is FULLY validated.
319             sub _coerce {
320 13     13   20 my $want = shift;
321 13 50       54 my $have = Scalar::Util::blessed($_[0]) ? shift : return undef;
322              
323             # In the simplest case it is already what we need
324 13 100       73 return $have if $have->isa($want);
325              
326             # Is there a coercion hint for this combination
327 8         21 my $key = ref($have) . ',' . $want;
328 8 100       40 my $hint = exists $hints{$key} ? $hints{$key}
    50          
329             : _resolve($want, ref($have), $key)
330             or return undef;
331              
332             # Call the coercion function
333 8         24 my $type = substr($hint, 0, 1, '');
334 8 100       58 if ( $type eq '>' ) {
    50          
    0          
335             # Direct Push
336 7         24 $have = $have->$hint();
337             } elsif ( $type eq '<' ) {
338             # Direct Pull
339 1         4 $have = $want->$hint($have);
340             } elsif ( $type eq '^' ) {
341             # Third party
342 0         0 my ($pkg, $function) = $hint =~ m/^(.*)::(.*)$/s;
343 0         0 require $pkg;
344 4     4   25 no strict 'refs';
  4         8  
  4         1319  
345 0         0 $have = &{"${pkg}::${function}"}($have);
  0         0  
346             } else {
347 0         0 Carp::croak("Unknown coercion hint '$type$hint'");
348             }
349              
350             # Did we get what we wanted?
351 8         101 _INSTANCE($have, $want);
352             }
353              
354             # Try to work out how to get from one class to the other class
355             sub _resolve {
356 4     4   8 my ($want, $have, $key) = @_;
357              
358             # Look for a __as method
359 4         10 my $method = "__as_$want";
360 4         12 $method =~ s/::/_/g;
361 4 100       50 return _hint($key, ">$method") if $have->can($method);
362              
363             # Look for a direct __from method
364 1         2 $method = "__from_$have";
365 1         2 $method =~ s/::/_/g;
366 1 50       8 return _hint($key, "<$method") if $want->can($method);
367              
368             # Give up (and don't try again).
369             # We use zero specifically so it will return false in boolean context
370 0         0 _hint($key, '0');
371             }
372              
373             # For now just save to the memory hash.
374             # Later, this may also involve saving to a database somewhere.
375             sub _hint {
376 4     4   22 $hints{$_[0]} = $_[1];
377             }
378              
379              
380              
381              
382              
383             #####################################################################
384             # Support Functions
385              
386             # Is a class loaded.
387             sub _loaded {
388 4     4   24 no strict 'refs';
  4         6  
  4         403  
389 9     9   22 foreach ( keys %{"$_[0]::"} ) {
  9         52  
390 8 50       47 return 1 unless substr($_, -2, 2) eq '::';
391             }
392 1         7 '';
393             }
394              
395             # Does a function exist.
396             sub _function_exists {
397 4     4   23 no strict 'refs';
  4         6  
  4         323  
398 7     7   19 defined &{"$_[0]::$_[1]"};
  7         59  
399             }
400              
401             1;
402              
403             =pod
404              
405             =head1 TO DO
406              
407             - Write more unit tests
408              
409             - Implement chained coercion
410              
411             - Provide a way to coerce to string, int, etc that is compatible with
412             L and other types of things.
413              
414             =head1 SUPPORT
415              
416             Bugs should always be submitted via the CPAN bug tracker
417              
418             L
419              
420             For other issues, contact the maintainer
421              
422             =head1 AUTHORS
423              
424             Adam Kennedy Eadamk@cpan.orgE
425              
426             =head1 COPYRIGHT
427              
428             Copyright 2004 - 2006 Adam Kennedy.
429              
430             This program is free software; you can redistribute
431             it and/or modify it under the same terms as Perl itself.
432              
433             The full text of the license can be found in the
434             LICENSE file included with this module.
435              
436             =cut