1 package Catalyst::Action;
5 Catalyst::Action - Catalyst Action
9 <form action="[%c.uri_for(c.action)%]">
11 $c->forward( $action->private_path );
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.
23 use Scalar::Util 'looks_like_number', 'blessed';
24 use Moose::Util::TypeConstraints ();
25 with 'MooseX::Emulate::Class::Accessor::Fast';
26 use namespace::clean -except => 'meta';
28 has class => (is => 'rw');
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');
35 reader => 'private_path',
39 default => sub { '/'.shift->reverse },
42 has number_of_args => (
48 builder=>'_build_number_of_args');
50 sub _build_number_of_args {
52 if( ! exists $self->attributes->{Args} ) {
53 # When 'Args' does not exist, that means we want 'any number of args'.
55 } elsif(!defined($self->attributes->{Args}[0])) {
56 # When its 'Args' that internal cue for 'unlimited'
59 scalar(@{$self->attributes->{Args}}) == 1 &&
60 looks_like_number($self->attributes->{Args}[0])
62 # 'Old school' numbered args (is allowed to be undef as well)
63 return $self->attributes->{Args}[0];
65 # New hotness named arg constraints
66 return $self->number_of_args_constraints;
70 sub normalized_arg_number {
71 return $_[0]->number_of_args;
74 sub comparable_arg_number {
75 return defined($_[0]->number_of_args) ? $_[0]->number_of_args : ~0;
78 has number_of_args_constraints => (
84 builder=>'_build_number_of_args_constraints');
86 sub _build_number_of_args_constraints {
88 return unless $self->has_args_constraints;
90 # If there is one constraint and its a ref, we need to decide
91 # if this number 'unknown' number or if the ref allows us to
94 if(scalar @{$self->args_constraints} == 1) {
95 my $tc = $self->args_constraints->[0];
97 $tc->can('is_strictly_a_type_of') &&
98 $tc->is_strictly_a_type_of('Tuple'))
100 my @parameters = @{ $tc->parameters||[]};
101 if( defined($parameters[-1]) and exists($parameters[-1]->{slurpy})) {
104 return my $total_params = scalar(@parameters);
106 } elsif($tc->is_a_type_of('Ref')) {
109 return 1; # Its a normal 1 arg type constraint.
112 # We need to loop through and error on ref types. We don't allow a ref type
115 foreach my $tc( @{$self->args_constraints}) {
116 if($tc->is_a_type_of('Ref')) {
117 die "$tc is a Ref type constraint. You cannot mix Ref and non Ref type constraints in Args for action ${\$self->reverse}";
126 has args_constraints => (
133 builder=>'_build_args_constraints',
135 has_args_constraints => 'count',
136 args_constraint_count => 'count',
137 all_args_constraints => 'elements',
140 sub _build_args_constraints {
142 my @arg_protos = @{$self->attributes->{Args}||[]};
144 return [] unless scalar(@arg_protos);
145 return [] unless defined($arg_protos[0]);
147 # If there is only one arg and it looks like a number
148 # we assume its 'classic' and the number is the number of
152 scalar(@arg_protos) == 1 &&
153 looks_like_number($arg_protos[0])
158 map { my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
164 has number_of_captures_constraints => (
170 builder=>'_build_number_of_capture_constraints');
172 sub _build_number_of_capture_constraints {
174 return unless $self->has_captures_constraints;
176 # If there is one constraint and its a ref, we need to decide
177 # if this number 'unknown' number or if the ref allows us to
178 # determine a length.
180 if(scalar @{$self->captures_constraints} == 1) {
181 my $tc = $self->captures_constraints->[0];
183 $tc->can('is_strictly_a_type_of') &&
184 $tc->is_strictly_a_type_of('Tuple'))
186 my @parameters = @{ $tc->parameters||[]};
187 if( defined($parameters[-1]) and exists($parameters[-1]->{slurpy})) {
190 return my $total_params = scalar(@parameters);
192 } elsif($tc->is_a_type_of('Ref')) {
193 die "You cannot use CaptureArgs($tc) in ${\$self->reverse} because we cannot determined the number of its parameters";
195 return 1; # Its a normal 1 arg type constraint.
198 # We need to loop through and error on ref types. We don't allow a ref type
201 foreach my $tc( @{$self->captures_constraints}) {
202 if($tc->is_a_type_of('Ref')) {
203 die "$tc is a Ref type constraint. You cannot mix Ref and non Ref type constraints in CaptureArgs for action ${\$self->reverse}";
212 has captures_constraints => (
219 builder=>'_build_captures_constraints',
221 has_captures_constraints => 'count',
222 captures_constraints_count => 'count',
223 all_captures_constraints => 'elements',
226 sub _build_captures_constraints {
228 my @arg_protos = @{$self->attributes->{CaptureArgs}||[]};
230 return [] unless scalar(@arg_protos);
231 return [] unless defined($arg_protos[0]);
232 # If there is only one arg and it looks like a number
233 # we assume its 'classic' and the number is the number of
237 scalar(@arg_protos) == 1 &&
238 looks_like_number($arg_protos[0])
243 map { my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
250 sub resolve_type_constraint {
251 my ($self, $name) = @_;
253 if(defined($name) && blessed($name) && $name->can('check')) {
254 # Its already a TC, good to go.
258 # This is broken for when there is more than one constraint
260 eval "use Type::Registry; 1" || die "Can't resolve type constraint $name without installing Type::Tiny";
261 my $tc = Type::Registry->new->foreign_lookup($name);
262 return defined $tc ? $tc : die "'$name' not a full namespace type constraint in ${\$self->private_path}";
265 my @tc = grep { defined $_ } (eval("package ${\$self->class}; $name"));
268 # ok... so its not defined in the package. we need to look at all the roles
269 # and superclasses, look for attributes and figure it out.
270 # Superclasses take precedence;
272 my @supers = $self->class->can('meta') ? map { $_->meta } $self->class->meta->superclasses : ();
273 my @roles = $self->class->can('meta') ? $self->class->meta->calculate_all_roles : ();
275 # So look through all the super and roles in order and return the
276 # first type constraint found. We should probably find all matching
277 # type constraints and try to do some sort of resolution.
279 foreach my $parent (@roles, @supers) {
280 if(my $m = $parent->get_method($self->name)) {
281 if($m->can('attributes')) {
282 my ($key, $value) = map { $_ =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ }
283 grep { $_=~/^Args\(/ or $_=~/^CaptureArgs\(/ }
285 next unless $value eq $name;
286 my @tc = eval "package ${\$parent->name}; $name";
288 return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
296 my $classes = join(',', $self->class, @roles, @supers);
297 die "'$name' not a type constraint in '${\$self->private_path}', Looked in: $classes";
301 return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
307 has number_of_captures => (
313 builder=>'_build_number_of_captures');
315 sub _build_number_of_captures {
317 if( ! exists $self->attributes->{CaptureArgs} ) {
318 # If there are no defined capture args, thats considered 0.
320 } elsif(!defined($self->attributes->{CaptureArgs}[0])) {
321 # If you fail to give a defined value, that's also 0
324 scalar(@{$self->attributes->{CaptureArgs}}) == 1 &&
325 looks_like_number($self->attributes->{CaptureArgs}[0])
327 # 'Old school' numbered captures
328 return $self->attributes->{CaptureArgs}[0];
330 # New hotness named arg constraints
331 return $self->number_of_captures_constraints;
338 # Stringify to reverse for debug output etc.
339 q{""} => sub { shift->{reverse} },
341 # Codulate to execute to invoke the encapsulated action coderef
342 '&{}' => sub { my $self = shift; sub { $self->execute(@_); }; },
344 # Make general $stuff still work
349 no warnings 'recursion';
351 sub dispatch { # Execute ourselves against a context
352 my ( $self, $c ) = @_;
353 return $c->execute( $self->class, $self );
362 my ( $self, $c ) = @_;
363 return $self->match_args($c, $c->req->args);
367 my ($self, $c, $args) = @_;
368 my @args = @{$args||[]};
370 # There there are arg constraints, we must see to it that the constraints
371 # check positive for each arg in the list.
372 if($self->has_args_constraints) {
373 # If there is only one type constraint, and its a Ref or subtype of Ref,
374 # That means we expect a reference, so use the full args arrayref.
376 $self->args_constraint_count == 1 &&
378 $self->args_constraints->[0]->is_a_type_of('Ref') ||
379 $self->args_constraints->[0]->is_a_type_of('ClassName')
382 # Ok, the the type constraint is a ref type, which is allowed to have
383 # any number of args. We need to check the arg length, if one is defined.
384 # If we had a ref type constraint that allowed us to determine the allowed
385 # number of args, we need to match that number. Otherwise if there was an
386 # undetermined number (~0) then we allow all the args. This is more of an
387 # Optimization since Tuple[Int, Int] would fail on 3,4,5 anyway, but this
388 # way we can avoid calling the constraint when the arg length is incorrect.
390 $self->comparable_arg_number == ~0 ||
391 scalar( @args ) == $self->comparable_arg_number
393 return $self->args_constraints->[0]->check($args);
397 # Removing coercion stuff for the first go
398 #if($self->args_constraints->[0]->coercion && $self->attributes->{Coerce}) {
399 # my $coerced = $self->args_constraints->[0]->coerce($c) || return 0;
400 # $c->req->args([$coerced]);
404 # Because of the way chaining works, we can expect args that are totally not
405 # what you'd expect length wise. When they don't match length, thats a fail
406 return 0 unless scalar( @args ) == $self->comparable_arg_number;
408 for my $i(0..$#args) {
409 $self->args_constraints->[$i]->check($args[$i]) || return 0;
414 # If infinite args with no constraints, we always match
415 return 1 if $self->comparable_arg_number == ~0;
417 # Otherwise, we just need to match the number of args.
418 return scalar( @args ) == $self->comparable_arg_number;
423 my ($self, $c, $captures) = @_;
424 my @captures = @{$captures||[]};
426 return 1 unless scalar(@captures); # If none, just say its ok
427 return $self->has_captures_constraints ?
428 $self->match_captures_constraints($c, $captures) : 1;
433 sub match_captures_constraints {
434 my ($self, $c, $captures) = @_;
435 my @captures = @{$captures||[]};
437 # Match is positive if you don't have any.
438 return 1 unless $self->has_captures_constraints;
441 $self->captures_constraints_count == 1 &&
443 $self->captures_constraints->[0]->is_a_type_of('Ref') ||
444 $self->captures_constraints->[0]->is_a_type_of('ClassName')
447 return $self->captures_constraints->[0]->check($captures);
449 for my $i(0..$#captures) {
450 $self->captures_constraints->[$i]->check($captures[$i]) || return 0;
460 return $a1->comparable_arg_number <=> $a2->comparable_arg_number;
464 return exists $_[0]->attributes->{Scheme} ? $_[0]->attributes->{Scheme}[0] : undef;
467 sub list_extra_info {
470 Args => $self->normalized_arg_number,
471 CaptureArgs => $self->number_of_captures,
475 __PACKAGE__->meta->make_immutable;
485 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.
490 Returns the name of the component where this action is defined.
491 Derived by calling the L<catalyst_component_name|Catalyst::Component/catalyst_component_name>
492 method on each component.
496 Returns a code reference to this action.
498 =head2 dispatch( $c )
500 Dispatch this action against a context.
502 =head2 execute( $controller, $c, @args )
504 Execute this action's coderef against a given controller with a given
505 context and arguments
509 Check Args attribute, and makes sure number of args matches the setting.
510 Always returns true if Args is omitted.
512 =head2 match_captures ($c, $captures)
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.
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).
521 =head2 match_captures_constraints ($c, \@captures);
523 Does the \@captures given match any constraints (if any constraints exist). Returns
524 true if you ask but there are no constraints.
526 =head2 match_args($c, $args)
528 Does the Args match or not?
530 =head2 resolve_type_constraint
532 Tries to find a type constraint if you have on on a type constrained method.
536 Compares 2 actions based on the value of the C<Args> attribute, with no C<Args>
537 having the highest precedence.
541 Returns the private namespace this action lives in.
545 Returns the private path for this action.
549 Returns absolute private path for this action. Unlike C<reverse>, the
550 C<private_path> of an action is always suitable for passing to C<forward>.
554 Returns the sub name of this action.
556 =head2 number_of_args
558 Returns the number of args this action expects. This is 0 if the action doesn't
559 take any arguments and undef if it will take any number of arguments.
561 =head2 normalized_arg_number
563 The number of arguments (starting with zero) that the current action defines, or
564 undefined if there is not defined number of args (which is later treated as, "
565 as many arguments as you like").
567 =head2 comparable_arg_number
569 For the purposes of comparison we normalize 'number_of_args' so that if it is
570 undef we mean ~0 (as many args are we can think of).
572 =head2 number_of_captures
574 Returns the number of captures this action expects for L<Chained|Catalyst::DispatchType::Chained> actions.
576 =head2 list_extra_info
578 A HashRef of key-values that an action can provide to a debugging screen
582 Any defined scheme for the action
590 Catalyst Contributors, see Catalyst.pm
594 This library is free software. You can redistribute it and/or modify it under
595 the same terms as Perl itself.