File Coverage

blib/lib/Catalyst/Action.pm
Criterion Covered Total %
statement 172 191 90.0
branch 99 130 76.1
condition 32 48 66.6
subroutine 27 28 96.4
pod 13 13 100.0
total 343 410 83.6


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