File Coverage

blib/lib/Params/CallbackRequest.pm
Criterion Covered Total %
statement 103 103 100.0
branch 58 68 85.2
condition 17 30 56.6
subroutine 12 12 100.0
pod 4 4 100.0
total 194 217 89.4


line stmt bran cond sub pod time code
1             package Params::CallbackRequest;
2              
3 7     7   109539 use strict;
  7         16  
  7         310  
4 7     7   3669 use Params::Validate ();
  7         46656  
  7         247  
5 7         71 use Params::CallbackRequest::Exceptions (abbr => [qw(throw_bad_params
6             throw_bad_key
7 7     7   2830 throw_cb_exec)]);
  7         19  
8              
9 7     7   40 use vars qw($VERSION);
  7         12  
  7         575  
10             $VERSION = '1.20';
11              
12             BEGIN {
13 7     7   18 for my $attr (qw( default_priority
14             default_pkg_key
15             redirected )) {
16 7     7   35 no strict 'refs';
  7         12  
  7         409  
17 21     2   76 *{$attr} = sub { $_[0]->{$attr} };
  21         2740  
  2         13  
18             }
19             }
20              
21             Params::Validate::validation_options
22             ( on_fail => sub { throw_bad_params join '', @_ } );
23              
24             # We'll use this code reference for cb_classes parameter validation.
25             my $valid_cb_classes = sub {
26             # Just return true if they use the string "ALL".
27             return 1 if $_[0] eq 'ALL';
28             # Return false if it isn't an array.
29             return unless ref $_[0] || '' eq 'ARRAY';
30             # Return true if the first value isn't the string "_ALL_";
31             return 1 if $_[0]->[0] ne '_ALL_';
32             # Return false if there's more than one element in the array.
33             return if @{$_[0]} > 1;
34             # Just return true.
35             return 1;
36             };
37              
38             # This is our default exception handler.
39             my $exception_handler = sub {
40             my $err = shift;
41             rethrow_exception($err) if ref $err;
42             throw_cb_exec error => "Error thrown by callback: $err",
43             callback_error => $err;
44             };
45              
46             # Set up the valid parameters to new().
47             my %valid_params = (
48             default_priority => {
49             type => Params::Validate::SCALAR,
50             callbacks => {
51             'valid priority' => sub { $_[0] =~ /^\d$/ }
52             },
53             default => 5,
54             },
55              
56             default_pkg_key => {
57             type => Params::Validate::SCALAR,
58             default => 'DEFAULT',
59             },
60              
61             callbacks => {
62             type => Params::Validate::ARRAYREF,
63             optional => 1,
64             },
65              
66             pre_callbacks => {
67             type => Params::Validate::ARRAYREF,
68             optional => 1,
69             },
70              
71             post_callbacks => {
72             type => Params::Validate::ARRAYREF,
73             optional => 1,
74             },
75              
76             cb_classes => {
77             type => Params::Validate::ARRAYREF | Params::Validate::SCALAR,
78             callbacks => { 'valid cb_classes' => $valid_cb_classes },
79             optional => 1,
80             },
81              
82             ignore_nulls => {
83             type => Params::Validate::BOOLEAN,
84             default => 0,
85             },
86              
87             exception_handler => {
88             type => Params::Validate::CODEREF,
89             default => $exception_handler
90             },
91              
92             leave_notes => {
93             type => Params::Validate::BOOLEAN,
94             default => 0,
95             },
96             );
97              
98             BEGIN {
99             # Load up any callback class definitions.
100 7     7   3542 require Params::Callback;
101 7         59 Params::Callback::_find_names();
102             }
103              
104             sub new {
105 20     20 1 32305 my $proto = shift;
106 20         1087 my %p = Params::Validate::validate(@_, \%valid_params);
107              
108             # Grab any class callback specifications.
109 19 100       248 @p{qw(_cbs _pre _post)} = Params::Callback->_load_classes($p{cb_classes})
110             if $p{cb_classes};
111              
112             # Process parameter-triggered callback specs.
113 19 100       138 if (my $cb_specs = delete $p{callbacks}) {
114 12         26 my %cbs;
115 12         32 foreach my $spec (@$cb_specs) {
116             # Set the default package key.
117 54   33     144 $spec->{pkg_key} ||= $p{default_pkg_key};
118              
119             # Make sure that we have a callback key.
120 54 100       219 throw_bad_params "Missing or invalid callback key"
121             unless $spec->{cb_key};
122              
123             # Make sure that we have a valid priority.
124 53 100       116 if (defined $spec->{priority}) {
125 28 100       101 throw_bad_params "Not a valid priority: '$spec->{priority}'"
126             unless $spec->{priority} =~ /^\d$/;
127             } else {
128             # Or use the default.
129 25         62 $spec->{priority} = $p{default_priority};
130             }
131              
132             # Make sure that we have a code reference.
133 52 100       163 throw_bad_params "Callback for package key '$spec->{pkg_key}' " .
134             "and callback key '$spec->{cb_key}' not a code reference"
135             unless ref $spec->{cb} eq 'CODE';
136              
137             # Make sure that the key isn't already in use.
138 51 100       193 throw_bad_params "Callback key '$spec->{cb_key}' already used " .
139             "by package key '$spec->{pkg_key}'"
140             if $p{_cbs}{$spec->{pkg_key}}->{$spec->{cb_key}};
141              
142             # Set it up.
143 50         269 $p{_cbs}{$spec->{pkg_key}}->{$spec->{cb_key}} =
144             { cb => $spec->{cb}, priority => $spec->{priority} };
145             }
146             }
147              
148             # Now validate and store any request callbacks.
149 15         44 foreach my $type (qw(pre post)) {
150 29 100       124 if (my $cbs = delete $p{$type . '_callbacks'}) {
151 6         9 my @gcbs;
152 6         14 foreach my $cb (@$cbs) {
153             # Make it an array unless Params::Callback has already
154             # done so.
155 6 50       72 $cb = [$cb, 'Params::Callback']
156             unless ref $cb eq 'ARRAY';
157             # Make sure that we have a code reference.
158 6 100       33 throw_bad_params "Request $type callback not a code reference"
159             unless ref $cb->[0] eq 'CODE';
160 5         12 push @gcbs, $cb;
161             }
162             # Keep 'em.
163 5         20 $p{"_$type"} = \@gcbs;
164             }
165             }
166              
167             # Warn 'em if they're not using any callbacks.
168 14 50 66     79 unless ($p{_cbs} or $p{_pre} or $p{_post}) {
      66        
169 1         12 require Carp;
170 1         192 Carp::carp("You didn't specify any callbacks.");
171             }
172              
173             # Set up the notes hash.
174 14         902 $p{notes} = {};
175              
176             # Let 'em have it.
177 14   33     222 return bless \%p, ref $proto || $proto;
178             }
179              
180             sub request {
181 104     104 1 30058 my ($self, $params) = (shift, shift);
182 104 50       285 return $self unless $params;
183 104 100       459 throw_bad_params "Parameter '$params' is not a hash reference"
184             unless UNIVERSAL::isa($params, 'HASH');
185              
186             # Use an array to store the callbacks according to their priorities. Why
187             # an array when most of its indices will be undefined? Well, because I
188             # benchmarked it vs. a hash, and found a very negligible difference when
189             # the array had only element five filled (with no 6-9 elements) and the
190             # hash had only one element. Furthermore, in all cases where the array had
191             # two elements (with the other 8 undef), it outperformed the two-element
192             # hash every time. But really this just starts to come down to very fine
193             # differences compared to the work that the callbacks will likely be
194             # doing, anyway. And in the meantime, the array is just easier to use,
195             # since the priorities are just numbers, and its easist to unshift and
196             # push on the request callbacks than to stick them onto a hash. In short,
197             # the use of arrays is cleaner, easier to read and maintain, and almost
198             # always just as fast or faster than using hashes. So that's the way it'll
199             # be.
200 103         185 my (@cbs, %cbhs);
201 103 50       344 if ($self->{_cbs}) {
202 103         282 foreach my $k (keys %$params) {
203             # Strip off the '.x' that an tag creates.
204 178         374 (my $chk = $k) =~ s/\.x$//;
205 178 100       1112 if ((my $key = $chk) =~ s/_cb(\d?)$//) {
206             # It's a callback field. Grab the priority.
207 143         278 my $priority = $1;
208              
209             # Skip callbacks without values, if necessary.
210 143 100 100     419 next if $self->{ignore_nulls} &&
      66        
211             (! defined $params->{$k} || $params->{$k} eq '');
212              
213 141 100       367 if ($chk ne $k) {
214             # Some browsers will submit $k.x and $k.y instead of just
215             # $k for , which is a field that can
216             # only be submitted once for a given page. So skip it if
217             # we've already seen this parameter.
218 7 50       259 next if exists $params->{$chk};
219             # Otherwise, add the unadorned key to $params with a true
220             # value.
221 7         21 $params->{$chk} = 1;
222             }
223              
224             # Find the package key and the callback key.
225 141         382 my ($pkg_key, $cb_key) = split /\|/, $key, 2;
226 141 50       314 next unless $pkg_key;
227              
228             # Find the callback.
229 141         202 my $cb;
230 141 100       466 my $class = $self->{_cbs}{$pkg_key} or
231             throw_bad_key error => "No such callback package " .
232             "'$pkg_key'",
233             callback_key => $chk;
234              
235 140 100       309 if (ref $class) {
236             # It's a functional callback. Grab it.
237 57 100       209 $cb = $class->{$cb_key}{cb} or
238             throw_bad_key error => "No callback found for " .
239             "callback key '$chk'",
240             callback_key => $chk;
241              
242             # Get the specified priority if none was included in the
243             # callback key.
244 56 100       149 $priority = $class->{$cb_key}{priority}
245             unless $priority ne '';
246 56         99 $class = 'Params::Callback';
247             } else {
248             # It's a method callback. Get it from the class.
249 83 50       411 $cb = $class->_get_callback($cb_key, \$priority) or
250             throw_bad_key error => "No callback found for " .
251             "callback key '$chk'",
252             callback_key => $chk;
253             }
254              
255             # Push the callback onto the stack, along with the parameters
256             # for the construction of the Params::Callback object that
257             # will be passed to it.
258 139   66     818 $cbhs{$class} ||= $class->new( @_,
259             params => $params,
260             cb_request => $self );
261 139         180 push @{$cbs[$priority]},
  139         971  
262             [ $cb, $cbhs{$class},
263             [ $priority, $cb_key, $pkg_key, $chk, $params->{$k} ]
264             ];
265             }
266             }
267             }
268              
269             # Put any pre and post request callbacks onto the stack.
270 101 100 66     430 if ($self->{_pre} or $self->{_post}) {
271 85         243 my $params = [ @_,
272             params => $params,
273             cb_request => $self ];
274 166   33     1166 unshift @cbs,
275 85         201 [ map { [ $_->[0], $cbhs{$_} || $_->[1]->new(@$params), [] ] }
276 85 50       224 @{$self->{_pre}} ]
277             if $self->{_pre};
278              
279 173   33     989 push @cbs,
280 85         185 [ map { [ $_->[0], $cbhs{$_} || $_->[1]->new(@$params), [] ] }
281 85 50       277 @{$self->{_post}} ]
282             if $self->{_post};
283             }
284              
285             # Now execute the callbacks.
286 101         171 eval {
287 101         412 foreach my $cb_list (@cbs) {
288             # Skip it if there are no callbacks for this priority.
289 670 100       18851 next unless $cb_list;
290 294         451 foreach my $cb_data (@$cb_list) {
291 452         9100 my ($cb, $cbh, $cbargs) = @$cb_data;
292             # Cheat! But this keeps them read-only for the client.
293 452         564 @{$cbh}{qw(priority cb_key pkg_key trigger_key value)} =
  452         1434  
294             @$cbargs;
295             # Execute the callback.
296 452         1327 $cb->($cbh);
297             }
298             }
299             };
300              
301             # Clear out the redirected attribute, the status, and notes.
302 101         7417 my $redir = delete $self->{redirected};
303 101         180 my $status = delete $self->{_status};
304 101 100       267 %{$self->{notes}} = () unless $self->{leave_notes};
  95         230  
305              
306 101 100       318 if (my $err = $@) {
307             # Just pass the exception to the exception handler unless it's an
308             # abort.
309 13 100       93 return $status if isa_cb_exception($err, 'Abort');
310 9         32 $self->{exception_handler}->($err);
311             }
312              
313             # We now return to normal processing.
314 89 50       1965 return $redir ? $status : $self;
315             }
316              
317             sub notes {
318 9     9 1 8 my $self = shift;
319 9 100       23 return $self->{notes} unless @_;
320 7         9 my $key = shift;
321             return @_
322 7 100       35 ? $self->{notes}{$key} = shift
323             : $self->{notes}{$key};
324             }
325              
326             sub clear_notes {
327 1     1 1 2 %{shift->{notes}} = ();
  1         7  
328             }
329              
330             1;
331             __END__