Stricter checking of attributes in Catalyst::DispatchType::Chained
[catagits/Catalyst-Runtime.git] / lib / Catalyst / DispatchType / Chained.pm
CommitLineData
5882c86e 1package Catalyst::DispatchType::Chained;
141459fa 2
3c0186f2 3use Moose;
e8b9f2a9 4extends 'Catalyst::DispatchType';
5
141459fa 6use Text::SimpleTable;
7use Catalyst::ActionChain;
39fc2ce1 8use Catalyst::Utils;
141459fa 9use URI;
dd97c1ac 10use Scalar::Util ();
141459fa 11
be5cb4e4 12has _endpoints => (
13 is => 'rw',
14 isa => 'ArrayRef',
15 required => 1,
16 default => sub{ [] },
17 );
18
19has _actions => (
20 is => 'rw',
21 isa => 'HashRef',
22 required => 1,
23 default => sub{ {} },
24 );
25
26has _children_of => (
27 is => 'rw',
28 isa => 'HashRef',
29 required => 1,
30 default => sub{ {} },
31 );
32
0fc2d522 33no Moose;
34
792b40ac 35# please don't perltidy this. hairy code within.
36
141459fa 37=head1 NAME
38
5882c86e 39Catalyst::DispatchType::Chained - Path Part DispatchType
141459fa 40
41=head1 SYNOPSIS
42
26dc649a 43Path part matching, allowing several actions to sequentially take care of processing a request:
44
05a90578 45 # root action - captures one argument after it
46 sub foo_setup : Chained('/') PathPart('foo') CaptureArgs(1) {
47 my ( $self, $c, $foo_arg ) = @_;
48 ...
49 }
50
51 # child action endpoint - takes one argument
52 sub bar : Chained('foo_setup') Args(1) {
53 my ( $self, $c, $bar_arg ) = @_;
54 ...
55 }
141459fa 56
57=head1 DESCRIPTION
58
26dc649a 59Dispatch type managing default behaviour. For more information on
60dispatch types, see:
61
62=over 4
63
b9b89145 64=item * L<Catalyst::Manual::Intro> for how they affect application authors
26dc649a 65
66=item * L<Catalyst::DispatchType> for implementation information.
67
68=back
05a90578 69
141459fa 70=head1 METHODS
71
72=head2 $self->list($c)
73
74Debug output for Path Part dispatch points
75
141459fa 76=cut
77
792b40ac 78sub list {
79 my ( $self, $c ) = @_;
80
be5cb4e4 81 return unless $self->_endpoints;
792b40ac 82
48d435ba 83 my $avail_width = Catalyst::Utils::term_width() - 9;
84 my $col1_width = ($avail_width * .50) < 35 ? 35 : int($avail_width * .50);
85 my $col2_width = $avail_width - $col1_width;
792b40ac 86 my $paths = Text::SimpleTable->new(
48d435ba 87 [ $col1_width, 'Path Spec' ], [ $col2_width, 'Private' ],
39fc2ce1 88 );
792b40ac 89
007a7ca0 90 my $has_unattached_actions;
91 my $unattached_actions = Text::SimpleTable->new(
48d435ba 92 [ $col1_width, 'Private' ], [ $col2_width, 'Missing parent' ],
007a7ca0 93 );
94
792b40ac 95 ENDPOINT: foreach my $endpoint (
96 sort { $a->reverse cmp $b->reverse }
be5cb4e4 97 @{ $self->_endpoints }
792b40ac 98 ) {
ffca3e96 99 my $args = $endpoint->list_extra_info->{Args};
792b40ac 100 my @parts = (defined($args) ? (("*") x $args) : '...');
d34667c3 101 my @parents = ();
792b40ac 102 my $parent = "DUMMY";
ba3f8a81 103 my $extra = $self->_list_extra_http_methods($endpoint);
792b40ac 104 my $curr = $endpoint;
105 while ($curr) {
ffca3e96 106 if (my $cap = $curr->list_extra_info->{CaptureArgs}) {
107 unshift(@parts, (("*") x $cap));
792b40ac 108 }
77771155 109 if (my $pp = $curr->attributes->{PathPart}) {
792b40ac 110 unshift(@parts, $pp->[0])
111 if (defined $pp->[0] && length $pp->[0]);
112 }
5882c86e 113 $parent = $curr->attributes->{Chained}->[0];
be5cb4e4 114 $curr = $self->_actions->{$parent};
d34667c3 115 unshift(@parents, $curr) if $curr;
792b40ac 116 }
007a7ca0 117 if ($parent ne '/') {
118 $has_unattached_actions = 1;
59d5a638 119 $unattached_actions->row('/' . ($parents[0] || $endpoint)->reverse, $parent);
007a7ca0 120 next ENDPOINT;
121 }
d34667c3 122 my @rows;
123 foreach my $p (@parents) {
124 my $name = "/${p}";
ba3f8a81 125
126 if (defined(my $extra = $self->_list_extra_http_methods($p))) {
127 $name = "${extra} ${name}";
128 }
ffca3e96 129 if (defined(my $cap = $p->list_extra_info->{CaptureArgs})) {
130 $name .= ' ('.$cap.')';
d34667c3 131 }
132 unless ($p eq $parents[0]) {
133 $name = "-> ${name}";
134 }
135 push(@rows, [ '', $name ]);
136 }
ba3f8a81 137 push(@rows, [ '', (@rows ? "=> " : '').($extra ? "$extra " : '')."/${endpoint}" ]);
f4624073 138 $rows[0][0] = join('/', '', @parts) || '/';
d34667c3 139 $paths->row(@$_) for @rows;
792b40ac 140 }
141
1cf0345b 142 $c->log->debug( "Loaded Chained actions:\n" . $paths->draw . "\n" );
007a7ca0 143 $c->log->debug( "Unattached Chained actions:\n", $unattached_actions->draw . "\n" )
144 if $has_unattached_actions;
792b40ac 145}
141459fa 146
ba3f8a81 147sub _list_extra_http_methods {
148 my ( $self, $action ) = @_;
149 return unless defined $action->list_extra_info->{HTTP_METHODS};
150 return join(', ', @{$action->list_extra_info->{HTTP_METHODS}});
151}
152
141459fa 153=head2 $self->match( $c, $path )
154
05a90578 155Calls C<recurse_match> to see if a chain matches the C<$path>.
141459fa 156
157=cut
158
159sub match {
160 my ( $self, $c, $path ) = @_;
161
e5ecd5bc 162 my $request = $c->request;
163 return 0 if @{$request->args};
141459fa 164
165 my @parts = split('/', $path);
166
6365b527 167 my ($chain, $captures, $parts) = $self->recurse_match($c, '/', \@parts);
634780e0 168
169 if ($parts && @$parts) {
170 for my $arg (@$parts) {
171 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
172 push @{$request->args}, $arg;
173 }
174 }
141459fa 175
176 return 0 unless $chain;
177
178 my $action = Catalyst::ActionChain->from_chain($chain);
179
e5ecd5bc 180 $request->action("/${action}");
181 $request->match("/${action}");
182 $request->captures($captures);
141459fa 183 $c->action($action);
184 $c->namespace( $action->namespace );
185
186 return 1;
187}
188
189=head2 $self->recurse_match( $c, $parent, \@path_parts )
190
05a90578 191Recursive search for a matching chain.
141459fa 192
193=cut
194
195sub recurse_match {
196 my ( $self, $c, $parent, $path_parts ) = @_;
be5cb4e4 197 my $children = $self->_children_of->{$parent};
141459fa 198 return () unless $children;
6b495723 199 my $best_action;
141459fa 200 my @captures;
1b04b972 201 TRY: foreach my $try_part (sort { length($b) <=> length($a) }
cdc97b63 202 keys %$children) {
1b04b972 203 # $b then $a to try longest part first
141459fa 204 my @parts = @$path_parts;
205 if (length $try_part) { # test and strip PathPart
206 next TRY unless
207 ($try_part eq join('/', # assemble equal number of parts
208 splice( # and strip them off @parts as well
792b40ac 209 @parts, 0, scalar(@{[split('/', $try_part)]})
210 ))); # @{[]} to avoid split to @_
141459fa 211 }
212 my @try_actions = @{$children->{$try_part}};
213 TRY_ACTION: foreach my $action (@try_actions) {
1c34f703 214 if (my $capture_attr = $action->attributes->{CaptureArgs}) {
3f91cb25 215 my $capture_count = $capture_attr->[0] || 0;
f505df49 216
217 # Short-circuit if not enough remaining parts
3f91cb25 218 next TRY_ACTION unless @parts >= $capture_count;
f505df49 219
141459fa 220 my @captures;
221 my @parts = @parts; # localise
7a7ac23c 222
1c34f703 223 # strip CaptureArgs into list
3f91cb25 224 push(@captures, splice(@parts, 0, $capture_count));
7a7ac23c 225
1279064a 226 # check if the action may fit, depending on a given test by the app
227 if ($action->can('match_captures')) { next TRY_ACTION unless $action->match_captures($c, \@captures) }
228
141459fa 229 # try the remaining parts against children of this action
0c78acb6 230 my ($actions, $captures, $action_parts, $n_pathparts) = $self->recurse_match(
141459fa 231 $c, '/'.$action->reverse, \@parts
232 );
2f381252 233 # No best action currently
234 # OR The action has less parts
235 # OR The action has equal parts but less captured data (ergo more defined)
236 if ($actions &&
237 (!$best_action ||
f4dbb497 238 $#$action_parts < $#{$best_action->{parts}} ||
2f381252 239 ($#$action_parts == $#{$best_action->{parts}} &&
0c78acb6 240 $#$captures < $#{$best_action->{captures}} &&
241 $n_pathparts > $best_action->{n_pathparts}))) {
242 my @pathparts = split /\//, $action->attributes->{PathPart}->[0];
6b495723 243 $best_action = {
244 actions => [ $action, @$actions ],
245 captures=> [ @captures, @$captures ],
0c78acb6 246 parts => $action_parts,
247 n_pathparts => scalar(@pathparts) + $n_pathparts,
248 };
6b495723 249 }
250 }
251 else {
7a7ac23c 252 {
253 local $c->req->{arguments} = [ @{$c->req->args}, @parts ];
254 next TRY_ACTION unless $action->match($c);
255 }
5c6d829d 256 my $args_attr = $action->attributes->{Args}->[0];
0c78acb6 257 my @pathparts = split /\//, $action->attributes->{PathPart}->[0];
953c176d 258 # No best action currently
259 # OR This one matches with fewer parts left than the current best action,
260 # And therefore is a better match
b0ad47c1 261 # OR No parts and this expects 0
953c176d 262 # The current best action might also be Args(0),
263 # but we couldn't chose between then anyway so we'll take the last seen
264
265 if (!$best_action ||
266 @parts < @{$best_action->{parts}} ||
a53e36d1 267 (!@parts && defined($args_attr) && $args_attr eq "0")){
6b495723 268 $best_action = {
269 actions => [ $action ],
270 captures=> [],
0c78acb6 271 parts => \@parts,
272 n_pathparts => scalar(@pathparts),
273 };
953c176d 274 }
141459fa 275 }
276 }
277 }
0c78acb6 278 return @$best_action{qw/actions captures parts n_pathparts/} if $best_action;
141459fa 279 return ();
280}
281
282=head2 $self->register( $c, $action )
283
05a90578 284Calls register_path for every Path attribute for the given $action.
141459fa 285
286=cut
287
2e29aec2 288sub _check_args_attr {
289 my ( $self, $action, $name ) = @_;
290
291 return unless exists $action->attributes->{$name};
292
293 if (@{$action->attributes->{$name}} > 1) {
294 Catalyst::Exception->throw(
295 "Multiple $name attributes not supported registering " . $action->reverse()
296 );
297 }
298 my $args = $action->attributes->{$name}->[0];
299 if (defined($args) and not (
300 Scalar::Util::looks_like_number($args) and
301 int($args) == $args
302 )) {
303 require Data::Dumper;
304 local $Data::Dumper::Terse = 1;
305 local $Data::Dumper::Indent = 0;
306 $args = Data::Dumper::Dumper($args);
307 Catalyst::Exception->throw(
308 "Invalid $name($args) for action " . $action->reverse() .
309 " (use '$name' or '$name(<number>)')"
310 );
311 }
312}
313
141459fa 314sub register {
315 my ( $self, $c, $action ) = @_;
316
1dc8af44 317 my @chained_attr = @{ $action->attributes->{Chained} || [] };
141459fa 318
1dc8af44 319 return 0 unless @chained_attr;
141459fa 320
2f381252 321 if (@chained_attr > 1) {
141459fa 322 Catalyst::Exception->throw(
5882c86e 323 "Multiple Chained attributes not supported registering ${action}"
141459fa 324 );
325 }
13c6b4cc 326 my $chained_to = $chained_attr[0];
141459fa 327
13c6b4cc 328 Catalyst::Exception->throw(
329 "Actions cannot chain to themselves registering /${action}"
330 ) if ($chained_to eq '/' . $action);
331
332 my $children = ($self->_children_of->{ $chained_to } ||= {});
141459fa 333
334 my @path_part = @{ $action->attributes->{PathPart} || [] };
335
09461385 336 my $part = $action->name;
141459fa 337
09461385 338 if (@path_part == 1 && defined $path_part[0]) {
339 $part = $path_part[0];
141459fa 340 } elsif (@path_part > 1) {
341 Catalyst::Exception->throw(
f3414019 342 "Multiple PathPart attributes not supported registering " . $action->reverse()
141459fa 343 );
344 }
345
8a6a6581 346 if ($part =~ m(^/)) {
347 Catalyst::Exception->throw(
f3414019 348 "Absolute parameters to PathPart not allowed registering " . $action->reverse()
8a6a6581 349 );
350 }
351
77771155 352 $action->attributes->{PathPart} = [ $part ];
792b40ac 353
141459fa 354 unshift(@{ $children->{$part} ||= [] }, $action);
355
be5cb4e4 356 $self->_actions->{'/'.$action->reverse} = $action;
792b40ac 357
2e29aec2 358 foreach my $name (qw(Args CaptureArgs)) {
359 $self->_check_args_attr($action, $name);
360 }
361
362 if (exists $action->attributes->{Args} and exists $action->attributes->{CaptureArgs}) {
363 Catalyst::Exception->throw(
364 "Combining Args and CaptureArgs not allowed registering " . $action->reverse()
365 );
dd97c1ac 366 }
367
1c34f703 368 unless ($action->attributes->{CaptureArgs}) {
be5cb4e4 369 unshift(@{ $self->_endpoints }, $action);
792b40ac 370 }
371
372 return 1;
141459fa 373}
374
375=head2 $self->uri_for_action($action, $captures)
376
05a90578 377Get the URI part for the action, using C<$captures> to fill
378the capturing parts.
141459fa 379
380=cut
381
382sub uri_for_action {
383 my ( $self, $action, $captures ) = @_;
384
5882c86e 385 return undef unless ($action->attributes->{Chained}
8b13f357 386 && !$action->attributes->{CaptureArgs});
792b40ac 387
388 my @parts = ();
389 my @captures = @$captures;
390 my $parent = "DUMMY";
391 my $curr = $action;
392 while ($curr) {
1c34f703 393 if (my $cap = $curr->attributes->{CaptureArgs}) {
84ac8ab9 394 return undef unless @captures >= ($cap->[0]||0); # not enough captures
8b13f357 395 if ($cap->[0]) {
f9155483 396 unshift(@parts, splice(@captures, -$cap->[0]));
8b13f357 397 }
792b40ac 398 }
77771155 399 if (my $pp = $curr->attributes->{PathPart}) {
792b40ac 400 unshift(@parts, $pp->[0])
8b13f357 401 if (defined($pp->[0]) && length($pp->[0]));
792b40ac 402 }
5882c86e 403 $parent = $curr->attributes->{Chained}->[0];
be5cb4e4 404 $curr = $self->_actions->{$parent};
141459fa 405 }
792b40ac 406
407 return undef unless $parent eq '/'; # fail for dangling action
408
409 return undef if @captures; # fail for too many captures
410
411 return join('/', '', @parts);
59d5a638 412
141459fa 413}
414
ae0e35ee 415=head2 $c->expand_action($action)
416
b0ad47c1 417Return a list of actions that represents a chained action. See
ae0e35ee 418L<Catalyst::Dispatcher> for more info. You probably want to
419use the expand_action it provides rather than this directly.
420
421=cut
422
52f71256 423sub expand_action {
424 my ($self, $action) = @_;
425
426 return unless $action->attributes && $action->attributes->{Chained};
427
428 my @chain;
429 my $curr = $action;
430
431 while ($curr) {
432 push @chain, $curr;
433 my $parent = $curr->attributes->{Chained}->[0];
434 $curr = $self->_actions->{$parent};
435 }
436
437 return Catalyst::ActionChain->from_chain([reverse @chain]);
438}
439
e5ecd5bc 440__PACKAGE__->meta->make_immutable;
f4de8c99 4411;
e5ecd5bc 442
05a90578 443=head1 USAGE
444
445=head2 Introduction
446
447The C<Chained> attribute allows you to chain public path parts together
67869327 448by their private names. A chain part's path can be specified with
449C<PathPart> and can be declared to expect an arbitrary number of
450arguments. The endpoint of the chain specifies how many arguments it
451gets through the C<Args> attribute. C<:Args(0)> would be none at all,
452C<:Args> without an integer would be unlimited. The path parts that
453aren't endpoints are using C<CaptureArgs> to specify how many parameters
454they expect to receive. As an example setup:
05a90578 455
456 package MyApp::Controller::Greeting;
457 use base qw/ Catalyst::Controller /;
458
459 # this is the beginning of our chain
460 sub hello : PathPart('hello') Chained('/') CaptureArgs(1) {
461 my ( $self, $c, $integer ) = @_;
462 $c->stash->{ message } = "Hello ";
463 $c->stash->{ arg_sum } = $integer;
464 }
465
466 # this is our endpoint, because it has no :CaptureArgs
467 sub world : PathPart('world') Chained('hello') Args(1) {
468 my ( $self, $c, $integer ) = @_;
469 $c->stash->{ message } .= "World!";
470 $c->stash->{ arg_sum } += $integer;
471
472 $c->response->body( join "<br/>\n" =>
473 $c->stash->{ message }, $c->stash->{ arg_sum } );
474 }
475
476The debug output provides a separate table for chained actions, showing
67869327 477the whole chain as it would match and the actions it contains. Here's an
478example of the startup output with our actions above:
05a90578 479
480 ...
481 [debug] Loaded Path Part actions:
482 .-----------------------+------------------------------.
483 | Path Spec | Private |
484 +-----------------------+------------------------------+
485 | /hello/*/world/* | /greeting/hello (1) |
486 | | => /greeting/world |
487 '-----------------------+------------------------------'
488 ...
489
67869327 490As you can see, Catalyst only deals with chains as whole paths and
491builds one for each endpoint, which are the actions with C<:Chained> but
492without C<:CaptureArgs>.
05a90578 493
494Let's assume this application gets a request at the path
67869327 495C</hello/23/world/12>. What happens then? First, Catalyst will dispatch
496to the C<hello> action and pass the value C<23> as an argument to it
497after the context. It does so because we have previously used
498C<:CaptureArgs(1)> to declare that it has one path part after itself as
499its argument. We told Catalyst that this is the beginning of the chain
500by specifying C<:Chained('/')>. Also note that instead of saying
501C<:PathPart('hello')> we could also just have said C<:PathPart>, as it
502defaults to the name of the action.
05a90578 503
504After C<hello> has run, Catalyst goes on to dispatch to the C<world>
67869327 505action. This is the last action to be called: Catalyst knows this is an
506endpoint because we did not specify a C<:CaptureArgs>
507attribute. Nevertheless we specify that this action expects an argument,
508but at this point we're using C<:Args(1)> to do that. We could also have
509said C<:Args> or left it out altogether, which would mean this action
510would get all arguments that are there. This action's C<:Chained>
511attribute says C<hello> and tells Catalyst that the C<hello> action in
512the current controller is its parent.
05a90578 513
514With this we have built a chain consisting of two public path parts.
67869327 515C<hello> captures one part of the path as its argument, and also
516specifies the path root as its parent. So this part is
517C</hello/$arg>. The next part is the endpoint C<world>, expecting one
518argument. It sums up to the path part C<world/$arg>. This leads to a
519complete chain of C</hello/$arg/world/$arg> which is matched against the
520requested paths.
521
522This example application would, if run and called by e.g.
523C</hello/23/world/12>, set the stash value C<message> to "Hello" and the
524value C<arg_sum> to "23". The C<world> action would then append "World!"
525to C<message> and add C<12> to the stash's C<arg_sum> value. For the
526sake of simplicity no view is shown. Instead we just put the values of
527the stash into our body. So the output would look like:
05a90578 528
529 Hello World!
530 35
531
67869327 532And our test server would have given us this debugging output for the
05a90578 533request:
534
535 ...
536 [debug] "GET" request for "hello/23/world/12" from "127.0.0.1"
537 [debug] Path is "/greeting/world"
538 [debug] Arguments are "12"
539 [info] Request took 0.164113s (6.093/s)
540 .------------------------------------------+-----------.
541 | Action | Time |
542 +------------------------------------------+-----------+
543 | /greeting/hello | 0.000029s |
544 | /greeting/world | 0.000024s |
545 '------------------------------------------+-----------'
546 ...
547
67869327 548What would be common uses of this dispatch technique? It gives the
549possibility to split up logic that contains steps that each depend on
550each other. An example would be, for example, a wiki path like
05a90578 551C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
552these actions:
553
554 sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
555 my ( $self, $c, $page_name ) = @_;
556 # load the page named $page_name and put the object
557 # into the stash
558 }
559
560 sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
561 my ( $self, $c, $revision_id ) = @_;
67869327 562 # use the page object in the stash to get at its
05a90578 563 # revision with number $revision_id
564 }
565
566 sub view : PathPart Chained('rev') Args(0) {
567 my ( $self, $c ) = @_;
67869327 568 # display the revision in our stash. Another option
05a90578 569 # would be to forward a compatible object to the action
570 # that displays the default wiki pages, unless we want
571 # a different interface here, for example restore
572 # functionality.
573 }
574
67869327 575It would now be possible to add other endpoints, for example C<restore>
576to restore this specific revision as the current state.
05a90578 577
67869327 578You don't have to put all the chained actions in one controller. The
579specification of the parent through C<:Chained> also takes an absolute
580action path as its argument. Just specify it with a leading C</>.
05a90578 581
582If you want, for example, to have actions for the public paths
67869327 583C</foo/12/edit> and C</foo/12>, just specify two actions with
05a90578 584C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
67869327 585path needs a C<:CaptureArgs(1)> attribute and a endpoint with
05a90578 586C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
587the action just a C<:Args(1)> to mark it as endpoint. This sums up to
588this debugging output:
589
590 ...
591 [debug] Loaded Path Part actions:
592 .-----------------------+------------------------------.
593 | Path Spec | Private |
594 +-----------------------+------------------------------+
595 | /foo/* | /controller/foo_view |
596 | /foo/*/edit | /controller/foo_load (1) |
597 | | => /controller/edit |
598 '-----------------------+------------------------------'
599 ...
600
b0ad47c1 601Here's a more detailed specification of the attributes belonging to
05a90578 602C<:Chained>:
603
604=head2 Attributes
605
606=over 8
607
608=item PathPart
609
610Sets the name of this part of the chain. If it is specified without
611arguments, it takes the name of the action as default. So basically
612C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
613This can also contain slashes to bind to a deeper level. An action
614with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
615C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
616effect as using C<:PathPart>, it would default to the action name.
617
d21a2b27 618=item PathPrefix
619
620Sets PathPart to the path_prefix of the current controller.
621
05a90578 622=item Chained
623
624Has to be specified for every child in the chain. Possible values are
d21a2b27 625absolute and relative private action paths or a single slash C</> to
626tell Catalyst that this is the root of a chain. The attribute
627C<:Chained> without arguments also defaults to the C</> behavior.
628Relative action paths may use C<../> to refer to actions in parent
629controllers.
05a90578 630
67869327 631Because you can specify an absolute path to the parent action, it
632doesn't matter to Catalyst where that parent is located. So, if your
633design requests it, you can redispatch a chain through any controller or
634namespace you want.
05a90578 635
636Another interesting possibility gives C<:Chained('.')>, which chains
67869327 637itself to an action with the path of the current controller's namespace.
05a90578 638For example:
639
640 # in MyApp::Controller::Foo
641 sub bar : Chained CaptureArgs(1) { ... }
642
643 # in MyApp::Controller::Foo::Bar
644 sub baz : Chained('.') Args(1) { ... }
645
646This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
67869327 647as the argument to Chained here chains the C<baz> action to an action
648with the path of the current controller namespace, namely
649C</foo/bar>. That action chains directly to C</>, so the C</bar/*/baz/*>
650chain comes out as the end product.
05a90578 651
d21a2b27 652=item ChainedParent
653
654Chains an action to another action with the same name in the parent
655controller. For Example:
656
657 # in MyApp::Controller::Foo
658 sub bar : Chained CaptureArgs(1) { ... }
659
660 # in MyApp::Controller::Foo::Moo
661 sub bar : ChainedParent Args(1) { ... }
662
663This builds a chain like C</bar/*/bar/*>.
664
05a90578 665=item CaptureArgs
666
67869327 667Must be specified for every part of the chain that is not an
05a90578 668endpoint. With this attribute Catalyst knows how many of the following
67869327 669parts of the path (separated by C</>) this action wants to capture as
670its arguments. If it doesn't expect any, just specify
671C<:CaptureArgs(0)>. The captures get passed to the action's C<@_> right
672after the context, but you can also find them as array references in
05a90578 673C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
674level of the action in the chain that captured the parts of the path.
675
67869327 676An action that is part of a chain (that is, one that has a C<:Chained>
677attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
678as a chain end.
05a90578 679
680=item Args
681
682By default, endpoints receive the rest of the arguments in the path. You
683can tell Catalyst through C<:Args> explicitly how many arguments your
684endpoint expects, just like you can with C<:CaptureArgs>. Note that this
67869327 685also affects whether this chain is invoked on a request. A chain with an
05a90578 686endpoint specifying one argument will only match if exactly one argument
687exists in the path.
688
689You can specify an exact number of arguments like C<:Args(3)>, including
690C<0>. If you just say C<:Args> without any arguments, it is the same as
67869327 691leaving it out altogether: The chain is matched regardless of the number
05a90578 692of path parts after the endpoint.
693
67869327 694Just as with C<:CaptureArgs>, the arguments get passed to the action in
05a90578 695C<@_> after the context object. They can also be reached through
696C<$c-E<gt>request-E<gt>arguments>.
697
698=back
699
67869327 700=head2 Auto actions, dispatching and forwarding
05a90578 701
702Note that the list of C<auto> actions called depends on the private path
67869327 703of the endpoint of the chain, not on the chained actions way. The
704C<auto> actions will be run before the chain dispatching begins. In
705every other aspect, C<auto> actions behave as documented.
05a90578 706
64111aca 707The C<forward>ing to other actions does just what you would expect. i.e.
e1207438 708only the target action is run. The actions that that action is chained
709to are not run.
710If you C<detach> out of a chain, the rest of the chain will not get
711called after the C<detach>.
05a90578 712
22faeff5 713=head2 match_captures
714
715A method which can optionally be implemented by actions to
716stop chain matching.
717
718See L<Catalyst::Action> for further details.
719
2f381252 720=head1 AUTHORS
141459fa 721
2f381252 722Catalyst Contributors, see Catalyst.pm
141459fa 723
724=head1 COPYRIGHT
725
536bee89 726This library is free software. You can redistribute it and/or modify it under
141459fa 727the same terms as Perl itself.
728
729=cut
730
7311;