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