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