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