don't mess with the class attribute
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Action.pm
CommitLineData
fbcc39ad 1package Catalyst::Action;
2
b2ddf6d7 3=head1 NAME
4
5Catalyst::Action - Catalyst Action
6
7=head1 SYNOPSIS
8
804fb55d 9 <form action="[%c.uri_for(c.action)%]">
85d9fce6 10
009b5b23 11 $c->forward( $action->private_path );
12
b2ddf6d7 13=head1 DESCRIPTION
14
43c58153 15This class represents a Catalyst Action. You can access the object for the
b2ddf6d7 16currently dispatched action via $c->action. See the L<Catalyst::Dispatcher>
17for more information on how actions are dispatched. Actions are defined in
18L<Catalyst::Controller> subclasses.
19
20=cut
21
059c085b 22use Moose;
59051400 23use Scalar::Util 'looks_like_number', 'blessed';
6d62355b 24use Moose::Util::TypeConstraints ();
241edc9b 25with 'MooseX::Emulate::Class::Accessor::Fast';
05b47f2e 26use namespace::clean -except => 'meta';
241edc9b 27
5fb12dbb 28has class => (is => 'rw');
191df47e 29has instance => (is=>'ro', required=>0, predicate=>'has_instance');
5fb12dbb 30has namespace => (is => 'rw');
31has 'reverse' => (is => 'rw');
32has attributes => (is => 'rw');
33has name => (is => 'rw');
34has code => (is => 'rw');
009b5b23 35has private_path => (
36 reader => 'private_path',
37 isa => 'Str',
38 lazy => 1,
39 required => 1,
40 default => sub { '/'.shift->reverse },
41);
059c085b 42
81436df9 43has 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 my $self = shift;
d4e8996f 53 if( ! exists $self->attributes->{Args} ) {
54 # When 'Args' does not exist, that means we want 'any number of args'.
55 return undef;
1d00b2ff 56 } elsif(!defined($self->attributes->{Args}[0])) {
81436df9 57 # When its 'Args' that internal cue for 'unlimited'
58 return undef;
4a0218ca 59 } elsif(
60 scalar(@{$self->attributes->{Args}}) == 1 &&
61 looks_like_number($self->attributes->{Args}[0])
62 ) {
a7ab9aa9 63 # 'Old school' numbered args (is allowed to be undef as well)
81436df9 64 return $self->attributes->{Args}[0];
65 } else {
d4e8996f 66 # New hotness named arg constraints
81436df9 67 return $self->number_of_args_constraints;
68 }
69 }
70
d4e8996f 71sub normalized_arg_number {
5dd46e24 72 return $_[0]->number_of_args;
73}
74
75sub comparable_arg_number {
d4e8996f 76 return defined($_[0]->number_of_args) ? $_[0]->number_of_args : ~0;
77}
78
bf4f1643 79has 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 my $self = shift;
89 return unless $self->has_args_constraints;
90
d9f0a350 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 if(scalar @{$self->args_constraints} == 1) {
96 my $tc = $self->args_constraints->[0];
97 if(
98 $tc->can('is_strictly_a_type_of') &&
99 $tc->is_strictly_a_type_of('Tuple'))
100 {
c2af9db8 101 my @parameters = @{ $tc->parameters||[] };
102 my $final = $parameters[-1];
103 if ( defined $final ) {
104 if ( blessed $final ) {
105 # modern form of slurpy
106 if ($final->can('is_strictly_a_type_of') && $final->is_strictly_a_type_of('Slurpy')) {
107 return undef;
108 }
109 }
110 else {
111 # old form of slurpy
112 if (ref $final eq 'HASH' && $final->{slurpy}) {
113 return undef;
114 }
115 }
bf4f1643 116 }
c2af9db8 117 return scalar @parameters;
d9f0a350 118 } elsif($tc->is_a_type_of('Ref')) {
119 return undef;
bf4f1643 120 } else {
d9f0a350 121 return 1; # Its a normal 1 arg type constraint.
bf4f1643 122 }
d9f0a350 123 } else {
79fb8f95 124 # We need to loop through and error on ref types. We don't allow a ref type
d9f0a350 125 # in the middle.
126 my $total = 0;
127 foreach my $tc( @{$self->args_constraints}) {
128 if($tc->is_a_type_of('Ref')) {
129 die "$tc is a Ref type constraint. You cannot mix Ref and non Ref type constraints in Args for action ${\$self->reverse}";
130 } else {
131 ++$total;
132 }
133 }
134 return $total;
bf4f1643 135 }
bf4f1643 136 }
137
6d62355b 138has args_constraints => (
139 is=>'ro',
81436df9 140 init_arg=>undef,
6d62355b 141 traits=>['Array'],
142 isa=>'ArrayRef',
143 required=>1,
144 lazy=>1,
145 builder=>'_build_args_constraints',
146 handles => {
147 has_args_constraints => 'count',
bf4f1643 148 args_constraint_count => 'count',
5dd46e24 149 all_args_constraints => 'elements',
6d62355b 150 });
151
152 sub _build_args_constraints {
153 my $self = shift;
154 my @arg_protos = @{$self->attributes->{Args}||[]};
155
156 return [] unless scalar(@arg_protos);
79b7db20 157 return [] unless defined($arg_protos[0]);
158
6d62355b 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 my @args = ();
163 if(
164 scalar(@arg_protos) == 1 &&
165 looks_like_number($arg_protos[0])
166 ) {
81436df9 167 return \@args;
6d62355b 168 } else {
4a0218ca 169 @args =
bf4f1643 170 map { my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
337a627a 171 @arg_protos;
6d62355b 172 }
6d62355b 173 return \@args;
174 }
175
bf4f1643 176has 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 my $self = shift;
186 return unless $self->has_captures_constraints;
187
d9f0a350 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 if(scalar @{$self->captures_constraints} == 1) {
193 my $tc = $self->captures_constraints->[0];
194 if(
195 $tc->can('is_strictly_a_type_of') &&
196 $tc->is_strictly_a_type_of('Tuple'))
197 {
198 my @parameters = @{ $tc->parameters||[]};
199 if( defined($parameters[-1]) and exists($parameters[-1]->{slurpy})) {
200 return undef;
bf4f1643 201 } else {
d9f0a350 202 return my $total_params = scalar(@parameters);
bf4f1643 203 }
d9f0a350 204 } elsif($tc->is_a_type_of('Ref')) {
205 die "You cannot use CaptureArgs($tc) in ${\$self->reverse} because we cannot determined the number of its parameters";
bf4f1643 206 } else {
d9f0a350 207 return 1; # Its a normal 1 arg type constraint.
208 }
209 } else {
79fb8f95 210 # We need to loop through and error on ref types. We don't allow a ref type
d9f0a350 211 # in the middle.
212 my $total = 0;
213 foreach my $tc( @{$self->captures_constraints}) {
214 if($tc->is_a_type_of('Ref')) {
215 die "$tc is a Ref type constraint. You cannot mix Ref and non Ref type constraints in CaptureArgs for action ${\$self->reverse}";
216 } else {
217 ++$total;
218 }
bf4f1643 219 }
d9f0a350 220 return $total;
bf4f1643 221 }
bf4f1643 222 }
223
a82c96cf 224has 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',
bf4f1643 234 captures_constraints_count => 'count',
5dd46e24 235 all_captures_constraints => 'elements',
a82c96cf 236 });
237
238 sub _build_captures_constraints {
239 my $self = shift;
240 my @arg_protos = @{$self->attributes->{CaptureArgs}||[]};
241
242 return [] unless scalar(@arg_protos);
79b7db20 243 return [] unless defined($arg_protos[0]);
a82c96cf 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 my @args = ();
248 if(
249 scalar(@arg_protos) == 1 &&
250 looks_like_number($arg_protos[0])
251 ) {
252 return \@args;
253 } else {
254 @args =
bf4f1643 255 map { my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
a82c96cf 256 @arg_protos;
257 }
258
259 return \@args;
260 }
261
842180f7 262sub resolve_type_constraint {
263 my ($self, $name) = @_;
59051400 264
265 if(defined($name) && blessed($name) && $name->can('check')) {
266 # Its already a TC, good to go.
267 return $name;
268 }
269
d2995a76 270 # This is broken for when there is more than one constraint
59051400 271 if($name=~m/::/) {
272 eval "use Type::Registry; 1" || die "Can't resolve type constraint $name without installing Type::Tiny";
273 my $tc = Type::Registry->new->foreign_lookup($name);
d2995a76 274 return defined $tc ? $tc : die "'$name' not a full namespace type constraint in ${\$self->private_path}";
59051400 275 }
88e5a8b0 276
d2995a76 277 my @tc = grep { defined $_ } (eval("package ${\$self->class}; $name"));
59051400 278
d2995a76 279 unless(scalar @tc) {
59051400 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;
d2995a76 283
a521afcc 284 my @supers = $self->class->can('meta') ? map { $_->meta } $self->class->meta->superclasses : ();
285 my @roles = $self->class->can('meta') ? $self->class->meta->calculate_all_roles : ();
59051400 286
79fb8f95 287 # So look through all the super and roles in order and return the
59051400 288 # first type constraint found. We should probably find all matching
289 # type constraints and try to do some sort of resolution.
290
291 foreach my $parent (@roles, @supers) {
292 if(my $m = $parent->get_method($self->name)) {
293 if($m->can('attributes')) {
294 my ($key, $value) = map { $_ =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ }
295 grep { $_=~/^Args\(/ or $_=~/^CaptureArgs\(/ }
296 @{$m->attributes};
297 next unless $value eq $name;
298 my @tc = eval "package ${\$parent->name}; $name";
d2995a76 299 if(scalar(@tc)) {
300 return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
301 } else {
302 return;
303 }
88e5a8b0 304 }
59051400 305 }
306 }
88e5a8b0 307
a521afcc 308 my $classes = join(',', $self->class, @roles, @supers);
309 die "'$name' not a type constraint in '${\$self->private_path}', Looked in: $classes";
d2995a76 310 }
59051400 311
d2995a76 312 if(scalar(@tc)) {
75ce30d0 313 return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
314 } else {
315 return;
316 }
842180f7 317}
318
a82c96cf 319has 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 my $self = shift;
329 if( ! exists $self->attributes->{CaptureArgs} ) {
330 # If there are no defined capture args, thats considered 0.
331 return 0;
332 } elsif(!defined($self->attributes->{CaptureArgs}[0])) {
333 # If you fail to give a defined value, that's also 0
334 return 0;
335 } elsif(
336 scalar(@{$self->attributes->{CaptureArgs}}) == 1 &&
337 looks_like_number($self->attributes->{CaptureArgs}[0])
338 ) {
339 # 'Old school' numbered captures
340 return $self->attributes->{CaptureArgs}[0];
341 } else {
342 # New hotness named arg constraints
343 return $self->number_of_captures_constraints;
344 }
345 }
346
347
2055d9ad 348use overload (
349
350 # Stringify to reverse for debug output etc.
351 q{""} => sub { shift->{reverse} },
352
353 # Codulate to execute to invoke the encapsulated action coderef
354 '&{}' => sub { my $self = shift; sub { $self->execute(@_); }; },
355
356 # Make general $stuff still work
357 fallback => 1,
358
359);
360
059c085b 361no warnings 'recursion';
362
b2ddf6d7 363sub dispatch { # Execute ourselves against a context
364 my ( $self, $c ) = @_;
191df47e 365 if($self->has_instance) {
366 return $c->execute( $self->instance, $self );
367 } else {
368 return $c->execute( $self->class, $self );
369 }
b2ddf6d7 370}
fbcc39ad 371
b2ddf6d7 372sub execute {
373 my $self = shift;
059c085b 374 $self->code->(@_);
b2ddf6d7 375}
fbcc39ad 376
b2ddf6d7 377sub match {
60034b8c 378 my ( $self, $c ) = @_;
c1192f1e 379 return $self->match_args($c, $c->req->args);
380}
381
382sub match_args {
383 my ($self, $c, $args) = @_;
384 my @args = @{$args||[]};
81436df9 385
d4e8996f 386 # There there are arg constraints, we must see to it that the constraints
387 # check positive for each arg in the list.
5d198e3f 388 if($self->has_args_constraints) {
4a0218ca 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 if(
bf4f1643 392 $self->args_constraint_count == 1 &&
a7ab9aa9 393 (
394 $self->args_constraints->[0]->is_a_type_of('Ref') ||
395 $self->args_constraints->[0]->is_a_type_of('ClassName')
396 )
4a0218ca 397 ) {
d9f0a350 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 if(
5dd46e24 406 $self->comparable_arg_number == ~0 ||
407 scalar( @args ) == $self->comparable_arg_number
d9f0a350 408 ) {
409 return $self->args_constraints->[0]->check($args);
410 } else {
411 return 0;
412 }
a7ab9aa9 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 #}
4a0218ca 419 } else {
a82c96cf 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
5dd46e24 422 return 0 unless scalar( @args ) == $self->comparable_arg_number;
a82c96cf 423
c1192f1e 424 for my $i(0..$#args) {
425 $self->args_constraints->[$i]->check($args[$i]) || return 0;
4a0218ca 426 }
427 return 1;
6d62355b 428 }
6d62355b 429 } else {
d9f0a350 430 # If infinite args with no constraints, we always match
5dd46e24 431 return 1 if $self->comparable_arg_number == ~0;
d9f0a350 432
d4e8996f 433 # Otherwise, we just need to match the number of args.
5dd46e24 434 return scalar( @args ) == $self->comparable_arg_number;
6d62355b 435 }
760d121e 436}
437
a82c96cf 438sub match_captures {
439 my ($self, $c, $captures) = @_;
440 my @captures = @{$captures||[]};
441
442 return 1 unless scalar(@captures); # If none, just say its ok
ec4d7259 443 return $self->has_captures_constraints ?
444 $self->match_captures_constraints($c, $captures) : 1;
a82c96cf 445
ec4d7259 446 return 1;
447}
448
449sub match_captures_constraints {
450 my ($self, $c, $captures) = @_;
451 my @captures = @{$captures||[]};
452
453 # Match is positive if you don't have any.
454 return 1 unless $self->has_captures_constraints;
455
456 if(
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 return $self->captures_constraints->[0]->check($captures);
a82c96cf 464 } else {
ec4d7259 465 for my $i(0..$#captures) {
466 $self->captures_constraints->[$i]->check($captures[$i]) || return 0;
467 }
a82c96cf 468 return 1;
ec4d7259 469 }
470
a82c96cf 471}
fbcc39ad 472
ec4d7259 473
05b47f2e 474sub compare {
475 my ($a1, $a2) = @_;
5dd46e24 476 return $a1->comparable_arg_number <=> $a2->comparable_arg_number;
05b47f2e 477}
478
62864794 479sub equals {
480 my ($self, $target) = @_;
481 return $self->private_path eq $target->private_path ? $self : 0;
482}
483
342d2169 484sub scheme {
485 return exists $_[0]->attributes->{Scheme} ? $_[0]->attributes->{Scheme}[0] : undef;
486}
487
ffca3e96 488sub list_extra_info {
489 my $self = shift;
490 return {
356e7503 491 Args => $self->normalized_arg_number,
ffca3e96 492 CaptureArgs => $self->number_of_captures,
493 }
88e5a8b0 494}
3c0da3ec 495
e5ecd5bc 496__PACKAGE__->meta->make_immutable;
497
b2ddf6d7 4981;
fbcc39ad 499
b2ddf6d7 500__END__
4ab87e27 501
fbcc39ad 502=head1 METHODS
503
b5ecfcf0 504=head2 attributes
fbcc39ad 505
4ab87e27 506The sub attributes that are set for this action, like Local, Path, Private
b2ddf6d7 507and so on. This determines how the action is dispatched to.
4ab87e27 508
b5ecfcf0 509=head2 class
b96f127f 510
4d38cb07 511Returns the name of the component where this action is defined.
f9818250 512Derived by calling the L<catalyst_component_name|Catalyst::Component/catalyst_component_name>
fb0c5b21 513method on each component.
4ab87e27 514
b5ecfcf0 515=head2 code
11bd4e3e 516
b2ddf6d7 517Returns a code reference to this action.
4ab87e27 518
b8f669f3 519=head2 dispatch( $c )
4ab87e27 520
18a9655c 521Dispatch this action against a context.
fbcc39ad 522
b8f669f3 523=head2 execute( $controller, $c, @args )
524
525Execute this action's coderef against a given controller with a given
526context and arguments
527
649fd1fa 528=head2 match( $c )
4ab87e27 529
649fd1fa 530Check Args attribute, and makes sure number of args matches the setting.
b2ddf6d7 531Always returns true if Args is omitted.
4082e678 532
760d121e 533=head2 match_captures ($c, $captures)
534
535Can be implemented by action class and action role authors. If the method
536exists, then it will be called with the request context and an array reference
537of the captures for this action.
538
539Returning true from this method causes the chain match to continue, returning
540makes the chain not match (and alternate, less preferred chains will be attempted).
541
ec4d7259 542=head2 match_captures_constraints ($c, \@captures);
543
544Does the \@captures given match any constraints (if any constraints exist). Returns
545true if you ask but there are no constraints.
546
c1192f1e 547=head2 match_args($c, $args)
548
75ce30d0 549Does the Args match or not?
c1192f1e 550
6f0b85d2 551=head2 resolve_type_constraint
552
79fb8f95 553Tries to find a type constraint if you have on on a type constrained method.
760d121e 554
91955398 555=head2 compare
556
cbe555e8 557Compares 2 actions based on the value of the C<Args> attribute, with no C<Args>
558having the highest precedence.
91955398 559
763d179f 560=head2 equals
62864794 561
562 if( $action->equal($other_action) ) { ... }
563
564Returns true if the two actions are equal.
565
b5ecfcf0 566=head2 namespace
fbcc39ad 567
4ab87e27 568Returns the private namespace this action lives in.
569
b5ecfcf0 570=head2 reverse
6b239949 571
4ab87e27 572Returns the private path for this action.
573
009b5b23 574=head2 private_path
575
576Returns absolute private path for this action. Unlike C<reverse>, the
577C<private_path> of an action is always suitable for passing to C<forward>.
578
b5ecfcf0 579=head2 name
fbcc39ad 580
18a9655c 581Returns the sub name of this action.
4ab87e27 582
0cff119a 583=head2 number_of_args
584
d4e8996f 585Returns the number of args this action expects. This is 0 if the action doesn't
586take any arguments and undef if it will take any number of arguments.
587
588=head2 normalized_arg_number
589
5dd46e24 590The number of arguments (starting with zero) that the current action defines, or
591undefined if there is not defined number of args (which is later treated as, "
592as many arguments as you like").
593
594=head2 comparable_arg_number
595
d4e8996f 596For the purposes of comparison we normalize 'number_of_args' so that if it is
597undef we mean ~0 (as many args are we can think of).
0cff119a 598
599=head2 number_of_captures
600
601Returns the number of captures this action expects for L<Chained|Catalyst::DispatchType::Chained> actions.
602
3c0da3ec 603=head2 list_extra_info
604
ffca3e96 605A HashRef of key-values that an action can provide to a debugging screen
3c0da3ec 606
342d2169 607=head2 scheme
608
609Any defined scheme for the action
610
059c085b 611=head2 meta
612
18a9655c 613Provided by Moose.
059c085b 614
2f381252 615=head1 AUTHORS
fbcc39ad 616
2f381252 617Catalyst Contributors, see Catalyst.pm
fbcc39ad 618
619=head1 COPYRIGHT
620
536bee89 621This library is free software. You can redistribute it and/or modify it under
fbcc39ad 622the same terms as Perl itself.
623
85d9fce6 624=cut
81436df9 625
626