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