File Coverage

blib/lib/Catalyst/Action.pm
Criterion Covered Total %
statement 174 193 90.1
branch 101 132 76.5
condition 32 48 66.6
subroutine 27 28 96.4
pod 13 13 100.0
total 347 414 83.8


line stmt bran cond sub pod time code
1             package Catalyst::Action;
2              
3             =head1 NAME
4              
5             Catalyst::Action - Catalyst Action
6              
7             =head1 SYNOPSIS
8              
9             <form action="[%c.uri_for(c.action)%]">
10              
11             $c->forward( $action->private_path );
12              
13             =head1 DESCRIPTION
14              
15             This class represents a Catalyst Action. You can access the object for the
16             currently dispatched action via $c->action. See the L<Catalyst::Dispatcher>
17             for more information on how actions are dispatched. Actions are defined in
18             L<Catalyst::Controller> subclasses.
19              
20             =cut
21              
22 156     156   39079 use Moose;
  156         342142  
  156         1207  
23 156     156   1032677 use Scalar::Util 'looks_like_number', 'blessed';
  156         501  
  156         11873  
24 156     156   1463 use Moose::Util::TypeConstraints ();
  156         488  
  156         6905  
25             with 'MooseX::Emulate::Class::Accessor::Fast';
26 156     156   1892 use namespace::clean -except => 'meta';
  156         7737  
  156         1870  
27              
28             has class => (is => 'rw');
29             has instance => (is=>'ro', required=>0, predicate=>'has_instance');
30             has namespace => (is => 'rw');
31             has 'reverse' => (is => 'rw');
32             has attributes => (is => 'rw');
33             has name => (is => 'rw');
34             has code => (is => 'rw');
35             has private_path => (
36             reader => 'private_path',
37             isa => 'Str',
38             lazy => 1,
39             required => 1,
40             default => sub { '/'.shift->reverse },
41             );
42              
43             has number_of_args => (
44             is=>'ro',
45             init_arg=>undef,
46             isa=>'Int|Undef',
47             required=>1,
48             lazy=>1,
49             builder=>'_build_number_of_args');
50              
51             sub _build_number_of_args {
52 2088     2088   3904 my $self = shift;
53 2088 100 66     56328 if( ! exists $self->attributes->{Args} ) {
    100          
    100          
54             # When 'Args' does not exist, that means we want 'any number of args'.
55 1599         43261 return undef;
56             } elsif(!defined($self->attributes->{Args}[0])) {
57             # When its 'Args' that internal cue for 'unlimited'
58 20         586 return undef;
59             } elsif(
60 469         12845 scalar(@{$self->attributes->{Args}}) == 1 &&
61             looks_like_number($self->attributes->{Args}[0])
62             ) {
63             # 'Old school' numbered args (is allowed to be undef as well)
64 443         12161 return $self->attributes->{Args}[0];
65             } else {
66             # New hotness named arg constraints
67 26         843 return $self->number_of_args_constraints;
68             }
69             }
70              
71             sub normalized_arg_number {
72 56     56 1 1521 return $_[0]->number_of_args;
73             }
74              
75             sub comparable_arg_number {
76 9701 100   9701 1 261994 return defined($_[0]->number_of_args) ? $_[0]->number_of_args : ~0;
77             }
78              
79             has number_of_args_constraints => (
80             is=>'ro',
81             isa=>'Int|Undef',
82             init_arg=>undef,
83             required=>1,
84             lazy=>1,
85             builder=>'_build_number_of_args_constraints');
86              
87             sub _build_number_of_args_constraints {
88 26     26   69 my $self = shift;
89 26 50       957 return unless $self->has_args_constraints;
90              
91             # If there is one constraint and its a ref, we need to decide
92             # if this number 'unknown' number or if the ref allows us to
93             # determine a length.
94              
95 26 100       45 if(scalar @{$self->args_constraints} == 1) {
  26         737  
96 24         651 my $tc = $self->args_constraints->[0];
97 24 100 100     206 if(
    100          
98             $tc->can('is_strictly_a_type_of') &&
99             $tc->is_strictly_a_type_of('Tuple'))
100             {
101 2 50       468 my @parameters = @{ $tc->parameters||[] };
  2         8  
102 2         18 my $final = $parameters[-1];
103 2 50       8 if ( defined $final ) {
104 2 100       8 if ( blessed $final ) {
105             # modern form of slurpy
106 1 50 33     6 if ($final->can('is_strictly_a_type_of') && $final->is_strictly_a_type_of('Slurpy')) {
107 0         0 return undef;
108             }
109             }
110             else {
111             # old form of slurpy
112 1 50 33     10 if (ref $final eq 'HASH' && $final->{slurpy}) {
113 1         44 return undef;
114             }
115             }
116             }
117 1         1735 return scalar @parameters;
118             } elsif($tc->is_a_type_of('Ref')) {
119 1         1082 return undef;
120             } else {
121 21         44975 return 1; # Its a normal 1 arg type constraint.
122             }
123             } else {
124             # We need to loop through and error on ref types. We don't allow a ref type
125             # in the middle.
126 2         7 my $total = 0;
127 2         4 foreach my $tc( @{$self->args_constraints}) {
  2         54  
128 4 50       25 if($tc->is_a_type_of('Ref')) {
129 0         0 die "$tc is a Ref type constraint. You cannot mix Ref and non Ref type constraints in Args for action ${\$self->reverse}";
  0         0  
130             } else {
131 4         4492 ++$total;
132             }
133             }
134 2         72 return $total;
135             }
136             }
137              
138             has args_constraints => (
139             is=>'ro',
140             init_arg=>undef,
141             traits=>['Array'],
142             isa=>'ArrayRef',
143             required=>1,
144             lazy=>1,
145             builder=>'_build_args_constraints',
146             handles => {
147             has_args_constraints => 'count',
148             args_constraint_count => 'count',
149             all_args_constraints => 'elements',
150             });
151              
152             sub _build_args_constraints {
153 571     571   1291 my $self = shift;
154 571 100       1124 my @arg_protos = @{$self->attributes->{Args}||[]};
  571         16916  
155              
156 571 100       13401 return [] unless scalar(@arg_protos);
157 260 100       1405 return [] unless defined($arg_protos[0]);
158              
159             # If there is only one arg and it looks like a number
160             # we assume its 'classic' and the number is the number of
161             # constraints.
162 244         537 my @args = ();
163 244 100 66     2050 if(
164             scalar(@arg_protos) == 1 &&
165             looks_like_number($arg_protos[0])
166             ) {
167 218         8745 return \@args;
168             } else {
169             @args =
170 26 50       73 map { my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
  26         104  
  26         381  
171             @arg_protos;
172             }
173 26         1082 return \@args;
174             }
175              
176             has number_of_captures_constraints => (
177             is=>'ro',
178             isa=>'Int|Undef',
179             init_arg=>undef,
180             required=>1,
181             lazy=>1,
182             builder=>'_build_number_of_capture_constraints');
183              
184             sub _build_number_of_capture_constraints {
185 6     6   19 my $self = shift;
186 6 50       236 return unless $self->has_captures_constraints;
187              
188             # If there is one constraint and its a ref, we need to decide
189             # if this number 'unknown' number or if the ref allows us to
190             # determine a length.
191              
192 6 100       14 if(scalar @{$self->captures_constraints} == 1) {
  6         178  
193 5         138 my $tc = $self->captures_constraints->[0];
194 5 100 100     36 if(
    50          
195             $tc->can('is_strictly_a_type_of') &&
196             $tc->is_strictly_a_type_of('Tuple'))
197             {
198 1 50       292 my @parameters = @{ $tc->parameters||[]};
  1         5  
199 1 50 33     14 if( defined($parameters[-1]) and exists($parameters[-1]->{slurpy})) {
200 0         0 return undef;
201             } else {
202 1         42 return my $total_params = scalar(@parameters);
203             }
204             } elsif($tc->is_a_type_of('Ref')) {
205 0         0 die "You cannot use CaptureArgs($tc) in ${\$self->reverse} because we cannot determined the number of its parameters";
  0         0  
206             } else {
207 4         6105 return 1; # Its a normal 1 arg type constraint.
208             }
209             } else {
210             # We need to loop through and error on ref types. We don't allow a ref type
211             # in the middle.
212 1         3 my $total = 0;
213 1         3 foreach my $tc( @{$self->captures_constraints}) {
  1         27  
214 2 50       7 if($tc->is_a_type_of('Ref')) {
215 0         0 die "$tc is a Ref type constraint. You cannot mix Ref and non Ref type constraints in CaptureArgs for action ${\$self->reverse}";
  0         0  
216             } else {
217 2         2330 ++$total;
218             }
219             }
220 1         38 return $total;
221             }
222             }
223              
224             has captures_constraints => (
225             is=>'ro',
226             init_arg=>undef,
227             traits=>['Array'],
228             isa=>'ArrayRef',
229             required=>1,
230             lazy=>1,
231             builder=>'_build_captures_constraints',
232             handles => {
233             has_captures_constraints => 'count',
234             captures_constraints_count => 'count',
235             all_captures_constraints => 'elements',
236             });
237              
238             sub _build_captures_constraints {
239 83     83   206 my $self = shift;
240 83 100       174 my @arg_protos = @{$self->attributes->{CaptureArgs}||[]};
  83         2350  
241              
242 83 100       856 return [] unless scalar(@arg_protos);
243 68 50       225 return [] unless defined($arg_protos[0]);
244             # If there is only one arg and it looks like a number
245             # we assume its 'classic' and the number is the number of
246             # constraints.
247 68         149 my @args = ();
248 68 100 66     471 if(
249             scalar(@arg_protos) == 1 &&
250             looks_like_number($arg_protos[0])
251             ) {
252 62         2521 return \@args;
253             } else {
254             @args =
255 6 50       29 map { my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
  6         22  
  6         148  
256             @arg_protos;
257             }
258              
259 6         292 return \@args;
260             }
261              
262             sub resolve_type_constraint {
263 32     32 1 91 my ($self, $name) = @_;
264              
265 32 50 33     199 if(defined($name) && blessed($name) && $name->can('check')) {
      33        
266             # Its already a TC, good to go.
267 0         0 return $name;
268             }
269              
270             # This is broken for when there is more than one constraint
271 32 100       121 if($name=~m/::/) {
272 1 50   1   117 eval "use Type::Registry; 1" || die "Can't resolve type constraint $name without installing Type::Tiny";
  1         9  
  1         2  
  1         572  
273 1         9 my $tc = Type::Registry->new->foreign_lookup($name);
274 1 50       183 return defined $tc ? $tc : die "'$name' not a full namespace type constraint in ${\$self->private_path}";
  0         0  
275             }
276              
277 31         61 my @tc = grep { defined $_ } (eval("package ${\$self->class}; $name"));
  32         24562  
  31         936  
278              
279 31 100       173 unless(scalar @tc) {
280             # ok... so its not defined in the package. we need to look at all the roles
281             # and superclasses, look for attributes and figure it out.
282             # Superclasses take precedence;
283              
284 2 50       68 my @supers = $self->class->can('meta') ? map { $_->meta } $self->class->meta->superclasses : ();
  2         222  
285 2 50       96 my @roles = $self->class->can('meta') ? $self->class->meta->calculate_all_roles : ();
286              
287             # So look through all the super and roles in order and return the
288             # first type constraint found. We should probably find all matching
289             # type constraints and try to do some sort of resolution.
290              
291 2         363 foreach my $parent (@roles, @supers) {
292 4 100       206 if(my $m = $parent->get_method($self->name)) {
293 2 50       99 if($m->can('attributes')) {
294 2         21 my ($key, $value) = map { $_ =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ }
295 4 100       228 grep { $_=~/^Args\(/ or $_=~/^CaptureArgs\(/ }
296 2         6 @{$m->attributes};
  2         88  
297 2 50       21 next unless $value eq $name;
298 2         5 my @tc = eval "package ${\$parent->name}; $name";
  2         147  
299 2 50       41 if(scalar(@tc)) {
300 2 50       5 return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
  2         17  
301             } else {
302 0         0 return;
303             }
304             }
305             }
306             }
307              
308 0         0 my $classes = join(',', $self->class, @roles, @supers);
309 0         0 die "'$name' not a type constraint in '${\$self->private_path}', Looked in: $classes";
  0         0  
310             }
311              
312 29 50       86 if(scalar(@tc)) {
313 29 100       61 return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
  32         340  
314             } else {
315 0         0 return;
316             }
317             }
318              
319             has number_of_captures => (
320             is=>'ro',
321             init_arg=>undef,
322             isa=>'Int',
323             required=>1,
324             lazy=>1,
325             builder=>'_build_number_of_captures');
326              
327             sub _build_number_of_captures {
328 148     148   349 my $self = shift;
329 148 100 66     4144 if( ! exists $self->attributes->{CaptureArgs} ) {
    50          
    100          
330             # If there are no defined capture args, thats considered 0.
331 30         873 return 0;
332             } elsif(!defined($self->attributes->{CaptureArgs}[0])) {
333             # If you fail to give a defined value, that's also 0
334 0         0 return 0;
335             } elsif(
336 118         3121 scalar(@{$self->attributes->{CaptureArgs}}) == 1 &&
337             looks_like_number($self->attributes->{CaptureArgs}[0])
338             ) {
339             # 'Old school' numbered captures
340 112         3110 return $self->attributes->{CaptureArgs}[0];
341             } else {
342             # New hotness named arg constraints
343 6         195 return $self->number_of_captures_constraints;
344             }
345             }
346              
347              
348             use overload (
349              
350             # Stringify to reverse for debug output etc.
351 59440     59440   209726 q{""} => sub { shift->{reverse} },
352              
353             # Codulate to execute to invoke the encapsulated action coderef
354 1     1   4 '&{}' => sub { my $self = shift; sub { $self->execute(@_); }; },
  1         37  
  1         14  
355              
356             # Make general $stuff still work
357 156         2794 fallback => 1,
358              
359 156     156   433361 );
  156         549  
360              
361 156     156   18896 no warnings 'recursion';
  156         597  
  156         141894  
362              
363             sub dispatch { # Execute ourselves against a context
364 9165     9165 1 19034 my ( $self, $c ) = @_;
365 9165 100       281800 if($self->has_instance) {
366 2         60 return $c->execute( $self->instance, $self );
367             } else {
368 9163         236627 return $c->execute( $self->class, $self );
369             }
370             }
371              
372             sub execute {
373 9165     9165 1 17222 my $self = shift;
374 9165         239793 $self->code->(@_);
375             }
376              
377             sub match {
378 1558     1558 1 4137 my ( $self, $c ) = @_;
379 1558         4931 return $self->match_args($c, $c->req->args);
380             }
381              
382             sub match_args {
383 1568     1568 1 4355 my ($self, $c, $args) = @_;
384 1568 50       2794 my @args = @{$args||[]};
  1568         5627  
385              
386             # There there are arg constraints, we must see to it that the constraints
387             # check positive for each arg in the list.
388 1568 100       60947 if($self->has_args_constraints) {
389             # If there is only one type constraint, and its a Ref or subtype of Ref,
390             # That means we expect a reference, so use the full args arrayref.
391 89 100 66     3332 if(
      100        
392             $self->args_constraint_count == 1 &&
393             (
394             $self->args_constraints->[0]->is_a_type_of('Ref') ||
395             $self->args_constraints->[0]->is_a_type_of('ClassName')
396             )
397             ) {
398             # Ok, the the type constraint is a ref type, which is allowed to have
399             # any number of args. We need to check the arg length, if one is defined.
400             # If we had a ref type constraint that allowed us to determine the allowed
401             # number of args, we need to match that number. Otherwise if there was an
402             # undetermined number (~0) then we allow all the args. This is more of an
403             # Optimization since Tuple[Int, Int] would fail on 3,4,5 anyway, but this
404             # way we can avoid calling the constraint when the arg length is incorrect.
405 8 100 100     5602 if(
406             $self->comparable_arg_number == ~0 ||
407             scalar( @args ) == $self->comparable_arg_number
408             ) {
409 7         203 return $self->args_constraints->[0]->check($args);
410             } else {
411 1         9 return 0;
412             }
413             # Removing coercion stuff for the first go
414             #if($self->args_constraints->[0]->coercion && $self->attributes->{Coerce}) {
415             # my $coerced = $self->args_constraints->[0]->coerce($c) || return 0;
416             # $c->req->args([$coerced]);
417             # return 1;
418             #}
419             } else {
420             # Because of the way chaining works, we can expect args that are totally not
421             # what you'd expect length wise. When they don't match length, thats a fail
422 81 100       84050 return 0 unless scalar( @args ) == $self->comparable_arg_number;
423              
424 44         193 for my $i(0..$#args) {
425 47 100       1500 $self->args_constraints->[$i]->check($args[$i]) || return 0;
426             }
427 27         726 return 1;
428             }
429             } else {
430             # If infinite args with no constraints, we always match
431 1479 100       5007 return 1 if $self->comparable_arg_number == ~0;
432              
433             # Otherwise, we just need to match the number of args.
434 839         2620 return scalar( @args ) == $self->comparable_arg_number;
435             }
436             }
437              
438             sub match_captures {
439 672     672 1 1583 my ($self, $c, $captures) = @_;
440 672 50       1021 my @captures = @{$captures||[]};
  672         1913  
441              
442 672 100       2148 return 1 unless scalar(@captures); # If none, just say its ok
443 462 100       18302 return $self->has_captures_constraints ?
444             $self->match_captures_constraints($c, $captures) : 1;
445              
446 0         0 return 1;
447             }
448              
449             sub match_captures_constraints {
450 51     51 1 122 my ($self, $c, $captures) = @_;
451 51 50       96 my @captures = @{$captures||[]};
  51         155  
452              
453             # Match is positive if you don't have any.
454 51 50       1940 return 1 unless $self->has_captures_constraints;
455              
456 51 100 66     1932 if(
      100        
457             $self->captures_constraints_count == 1 &&
458             (
459             $self->captures_constraints->[0]->is_a_type_of('Ref') ||
460             $self->captures_constraints->[0]->is_a_type_of('ClassName')
461             )
462             ) {
463 13         11897 return $self->captures_constraints->[0]->check($captures);
464             } else {
465 38         29773 for my $i(0..$#captures) {
466 45 100       1520 $self->captures_constraints->[$i]->check($captures[$i]) || return 0;
467             }
468 23         511 return 1;
469             }
470              
471             }
472              
473              
474             sub compare {
475 3497     3497 1 7295 my ($a1, $a2) = @_;
476 3497         7728 return $a1->comparable_arg_number <=> $a2->comparable_arg_number;
477             }
478              
479             sub equals {
480 0     0 1 0 my ($self, $target) = @_;
481 0 0       0 return $self->private_path eq $target->private_path ? $self : 0;
482             }
483              
484             sub scheme {
485 32 100   32 1 889 return exists $_[0]->attributes->{Scheme} ? $_[0]->attributes->{Scheme}[0] : undef;
486             }
487              
488             sub list_extra_info {
489 55     55 1 85 my $self = shift;
490             return {
491 55         107 Args => $self->normalized_arg_number,
492             CaptureArgs => $self->number_of_captures,
493             }
494             }
495              
496             __PACKAGE__->meta->make_immutable;
497              
498             1;
499              
500             __END__
501              
502             =head1 METHODS
503              
504             =head2 attributes
505              
506             The sub attributes that are set for this action, like Local, Path, Private
507             and so on. This determines how the action is dispatched to.
508              
509             =head2 class
510              
511             Returns the name of the component where this action is defined.
512             Derived by calling the L<catalyst_component_name|Catalyst::Component/catalyst_component_name>
513             method on each component.
514              
515             =head2 code
516              
517             Returns a code reference to this action.
518              
519             =head2 dispatch( $c )
520              
521             Dispatch this action against a context.
522              
523             =head2 execute( $controller, $c, @args )
524              
525             Execute this action's coderef against a given controller with a given
526             context and arguments
527              
528             =head2 match( $c )
529              
530             Check Args attribute, and makes sure number of args matches the setting.
531             Always returns true if Args is omitted.
532              
533             =head2 match_captures ($c, $captures)
534              
535             Can be implemented by action class and action role authors. If the method
536             exists, then it will be called with the request context and an array reference
537             of the captures for this action.
538              
539             Returning true from this method causes the chain match to continue, returning
540             makes the chain not match (and alternate, less preferred chains will be attempted).
541              
542             =head2 match_captures_constraints ($c, \@captures);
543              
544             Does the \@captures given match any constraints (if any constraints exist). Returns
545             true if you ask but there are no constraints.
546              
547             =head2 match_args($c, $args)
548              
549             Does the Args match or not?
550              
551             =head2 resolve_type_constraint
552              
553             Tries to find a type constraint if you have on on a type constrained method.
554              
555             =head2 compare
556              
557             Compares 2 actions based on the value of the C<Args> attribute, with no C<Args>
558             having the highest precedence.
559              
560             =head2 equals
561              
562             if( $action->equal($other_action) ) { ... }
563              
564             Returns true if the two actions are equal.
565              
566             =head2 namespace
567              
568             Returns the private namespace this action lives in.
569              
570             =head2 reverse
571              
572             Returns the private path for this action.
573              
574             =head2 private_path
575              
576             Returns absolute private path for this action. Unlike C<reverse>, the
577             C<private_path> of an action is always suitable for passing to C<forward>.
578              
579             =head2 name
580              
581             Returns the sub name of this action.
582              
583             =head2 number_of_args
584              
585             Returns the number of args this action expects. This is 0 if the action doesn't
586             take any arguments and undef if it will take any number of arguments.
587              
588             =head2 normalized_arg_number
589              
590             The number of arguments (starting with zero) that the current action defines, or
591             undefined if there is not defined number of args (which is later treated as, "
592             as many arguments as you like").
593              
594             =head2 comparable_arg_number
595              
596             For the purposes of comparison we normalize 'number_of_args' so that if it is
597             undef we mean ~0 (as many args are we can think of).
598              
599             =head2 number_of_captures
600              
601             Returns the number of captures this action expects for L<Chained|Catalyst::DispatchType::Chained> actions.
602              
603             =head2 list_extra_info
604              
605             A HashRef of key-values that an action can provide to a debugging screen
606              
607             =head2 scheme
608              
609             Any defined scheme for the action
610              
611             =head2 meta
612              
613             Provided by Moose.
614              
615             =head1 AUTHORS
616              
617             Catalyst Contributors, see Catalyst.pm
618              
619             =head1 COPYRIGHT
620              
621             This library is free software. You can redistribute it and/or modify it under
622             the same terms as Perl itself.
623              
624             =cut
625              
626