Document :ChainedParent.
[catagits/Catalyst-Runtime.git] / lib / Catalyst / DispatchType / Chained.pm
CommitLineData
5882c86e 1package Catalyst::DispatchType::Chained;
141459fa 2
3use strict;
4use base qw/Catalyst::DispatchType/;
5use Text::SimpleTable;
6use Catalyst::ActionChain;
7use URI;
8
792b40ac 9# please don't perltidy this. hairy code within.
10
141459fa 11=head1 NAME
12
5882c86e 13Catalyst::DispatchType::Chained - Path Part DispatchType
141459fa 14
15=head1 SYNOPSIS
16
05a90578 17 # root action - captures one argument after it
18 sub foo_setup : Chained('/') PathPart('foo') CaptureArgs(1) {
19 my ( $self, $c, $foo_arg ) = @_;
20 ...
21 }
22
23 # child action endpoint - takes one argument
24 sub bar : Chained('foo_setup') Args(1) {
25 my ( $self, $c, $bar_arg ) = @_;
26 ...
27 }
141459fa 28
29=head1 DESCRIPTION
30
05a90578 31See L</USAGE>.
32
141459fa 33=head1 METHODS
34
35=head2 $self->list($c)
36
37Debug output for Path Part dispatch points
38
141459fa 39=cut
40
792b40ac 41sub list {
42 my ( $self, $c ) = @_;
43
44 return unless $self->{endpoints};
45
46 my $paths = Text::SimpleTable->new(
47 [ 35, 'Path Spec' ], [ 36, 'Private' ]
48 );
49
50 ENDPOINT: foreach my $endpoint (
51 sort { $a->reverse cmp $b->reverse }
52 @{ $self->{endpoints} }
53 ) {
54 my $args = $endpoint->attributes->{Args}->[0];
55 my @parts = (defined($args) ? (("*") x $args) : '...');
d34667c3 56 my @parents = ();
792b40ac 57 my $parent = "DUMMY";
58 my $curr = $endpoint;
59 while ($curr) {
1c34f703 60 if (my $cap = $curr->attributes->{CaptureArgs}) {
792b40ac 61 unshift(@parts, (("*") x $cap->[0]));
62 }
63 if (my $pp = $curr->attributes->{PartPath}) {
64 unshift(@parts, $pp->[0])
65 if (defined $pp->[0] && length $pp->[0]);
66 }
5882c86e 67 $parent = $curr->attributes->{Chained}->[0];
792b40ac 68 $curr = $self->{actions}{$parent};
d34667c3 69 unshift(@parents, $curr) if $curr;
792b40ac 70 }
71 next ENDPOINT unless $parent eq '/'; # skip dangling action
d34667c3 72 my @rows;
73 foreach my $p (@parents) {
74 my $name = "/${p}";
1c34f703 75 if (my $cap = $p->attributes->{CaptureArgs}) {
d34667c3 76 $name .= ' ('.$cap->[0].')';
77 }
78 unless ($p eq $parents[0]) {
79 $name = "-> ${name}";
80 }
81 push(@rows, [ '', $name ]);
82 }
83 push(@rows, [ '', (@rows ? "=> " : '')."/${endpoint}" ]);
84 $rows[0][0] = join('/', '', @parts);
85 $paths->row(@$_) for @rows;
792b40ac 86 }
87
1cf0345b 88 $c->log->debug( "Loaded Chained actions:\n" . $paths->draw . "\n" );
792b40ac 89}
141459fa 90
91=head2 $self->match( $c, $path )
92
05a90578 93Calls C<recurse_match> to see if a chain matches the C<$path>.
141459fa 94
95=cut
96
97sub match {
98 my ( $self, $c, $path ) = @_;
99
100 return 0 if @{$c->req->args};
101
102 my @parts = split('/', $path);
103
6365b527 104 my ($chain, $captures, $parts) = $self->recurse_match($c, '/', \@parts);
6b495723 105 push @{$c->req->args}, @$parts if $parts && @$parts;
141459fa 106
107 return 0 unless $chain;
108
109 my $action = Catalyst::ActionChain->from_chain($chain);
110
111 $c->req->action("/${action}");
112 $c->req->match("/${action}");
113 $c->req->captures($captures);
114 $c->action($action);
115 $c->namespace( $action->namespace );
116
117 return 1;
118}
119
120=head2 $self->recurse_match( $c, $parent, \@path_parts )
121
05a90578 122Recursive search for a matching chain.
141459fa 123
124=cut
125
126sub recurse_match {
127 my ( $self, $c, $parent, $path_parts ) = @_;
128 my $children = $self->{children_of}{$parent};
129 return () unless $children;
6b495723 130 my $best_action;
141459fa 131 my @captures;
1b04b972 132 TRY: foreach my $try_part (sort { length($b) <=> length($a) }
cdc97b63 133 keys %$children) {
1b04b972 134 # $b then $a to try longest part first
141459fa 135 my @parts = @$path_parts;
136 if (length $try_part) { # test and strip PathPart
137 next TRY unless
138 ($try_part eq join('/', # assemble equal number of parts
139 splice( # and strip them off @parts as well
792b40ac 140 @parts, 0, scalar(@{[split('/', $try_part)]})
141 ))); # @{[]} to avoid split to @_
141459fa 142 }
143 my @try_actions = @{$children->{$try_part}};
144 TRY_ACTION: foreach my $action (@try_actions) {
1c34f703 145 if (my $capture_attr = $action->attributes->{CaptureArgs}) {
f505df49 146
147 # Short-circuit if not enough remaining parts
148 next TRY_ACTION unless @parts >= $capture_attr->[0];
149
141459fa 150 my @captures;
151 my @parts = @parts; # localise
7a7ac23c 152
1c34f703 153 # strip CaptureArgs into list
7a7ac23c 154 push(@captures, splice(@parts, 0, $capture_attr->[0]));
155
141459fa 156 # try the remaining parts against children of this action
6365b527 157 my ($actions, $captures, $action_parts) = $self->recurse_match(
141459fa 158 $c, '/'.$action->reverse, \@parts
159 );
4cea2e82 160 # No best action currently
161 # OR The action has less parts
162 # OR The action has equal parts but less captured data (ergo more defined)
163 if ($actions &&
164 (!$best_action ||
165 $#$action_parts < $#{$best_action->{parts}} ||
166 ($#$action_parts == $#{$best_action->{parts}} &&
167 $#$captures < $#{$best_action->{captures}}))){
6b495723 168 $best_action = {
169 actions => [ $action, @$actions ],
170 captures=> [ @captures, @$captures ],
171 parts => $action_parts
172 };
173 }
174 }
175 else {
7a7ac23c 176 {
177 local $c->req->{arguments} = [ @{$c->req->args}, @parts ];
178 next TRY_ACTION unless $action->match($c);
179 }
953c176d 180 my $args_attr = $action->attributes->{Args}->[0];
181
182 # No best action currently
183 # OR This one matches with fewer parts left than the current best action,
184 # And therefore is a better match
185 # OR No parts and this expects 0
186 # The current best action might also be Args(0),
187 # but we couldn't chose between then anyway so we'll take the last seen
188
189 if (!$best_action ||
190 @parts < @{$best_action->{parts}} ||
a8194217 191 (!@parts && $args_attr eq 0)){
6b495723 192 $best_action = {
193 actions => [ $action ],
194 captures=> [],
195 parts => \@parts
6b495723 196 }
953c176d 197 }
141459fa 198 }
199 }
200 }
953c176d 201 return @$best_action{qw/actions captures parts/} if $best_action;
141459fa 202 return ();
203}
204
205=head2 $self->register( $c, $action )
206
05a90578 207Calls register_path for every Path attribute for the given $action.
141459fa 208
209=cut
210
211sub register {
212 my ( $self, $c, $action ) = @_;
213
1dc8af44 214 my @chained_attr = @{ $action->attributes->{Chained} || [] };
141459fa 215
1dc8af44 216 return 0 unless @chained_attr;
141459fa 217
57d7305c 218 if (@chained_attr > 1) {
141459fa 219 Catalyst::Exception->throw(
5882c86e 220 "Multiple Chained attributes not supported registering ${action}"
141459fa 221 );
222 }
223
f3107403 224 my $children = ($self->{children_of}{ $chained_attr[0] } ||= {});
141459fa 225
226 my @path_part = @{ $action->attributes->{PathPart} || [] };
227
09461385 228 my $part = $action->name;
141459fa 229
09461385 230 if (@path_part == 1 && defined $path_part[0]) {
231 $part = $path_part[0];
141459fa 232 } elsif (@path_part > 1) {
233 Catalyst::Exception->throw(
234 "Multiple PathPart attributes not supported registering ${action}"
235 );
236 }
237
8a6a6581 238 if ($part =~ m(^/)) {
239 Catalyst::Exception->throw(
240 "Absolute parameters to PathPart not allowed registering ${action}"
241 );
242 }
243
792b40ac 244 $action->attributes->{PartPath} = [ $part ];
245
141459fa 246 unshift(@{ $children->{$part} ||= [] }, $action);
247
792b40ac 248 ($self->{actions} ||= {})->{'/'.$action->reverse} = $action;
249
1c34f703 250 unless ($action->attributes->{CaptureArgs}) {
792b40ac 251 unshift(@{ $self->{endpoints} ||= [] }, $action);
252 }
253
254 return 1;
141459fa 255}
256
257=head2 $self->uri_for_action($action, $captures)
258
05a90578 259Get the URI part for the action, using C<$captures> to fill
260the capturing parts.
141459fa 261
262=cut
263
264sub uri_for_action {
265 my ( $self, $action, $captures ) = @_;
266
5882c86e 267 return undef unless ($action->attributes->{Chained}
8b13f357 268 && !$action->attributes->{CaptureArgs});
792b40ac 269
270 my @parts = ();
271 my @captures = @$captures;
272 my $parent = "DUMMY";
273 my $curr = $action;
274 while ($curr) {
1c34f703 275 if (my $cap = $curr->attributes->{CaptureArgs}) {
792b40ac 276 return undef unless @captures >= $cap->[0]; # not enough captures
8b13f357 277 if ($cap->[0]) {
278 unshift(@parts, splice(@captures, -$cap->[0]));
279 }
792b40ac 280 }
281 if (my $pp = $curr->attributes->{PartPath}) {
282 unshift(@parts, $pp->[0])
8b13f357 283 if (defined($pp->[0]) && length($pp->[0]));
792b40ac 284 }
5882c86e 285 $parent = $curr->attributes->{Chained}->[0];
792b40ac 286 $curr = $self->{actions}{$parent};
141459fa 287 }
792b40ac 288
289 return undef unless $parent eq '/'; # fail for dangling action
290
291 return undef if @captures; # fail for too many captures
292
293 return join('/', '', @parts);
294
141459fa 295}
296
05a90578 297=head1 USAGE
298
299=head2 Introduction
300
301The C<Chained> attribute allows you to chain public path parts together
67869327 302by their private names. A chain part's path can be specified with
303C<PathPart> and can be declared to expect an arbitrary number of
304arguments. The endpoint of the chain specifies how many arguments it
305gets through the C<Args> attribute. C<:Args(0)> would be none at all,
306C<:Args> without an integer would be unlimited. The path parts that
307aren't endpoints are using C<CaptureArgs> to specify how many parameters
308they expect to receive. As an example setup:
05a90578 309
310 package MyApp::Controller::Greeting;
311 use base qw/ Catalyst::Controller /;
312
313 # this is the beginning of our chain
314 sub hello : PathPart('hello') Chained('/') CaptureArgs(1) {
315 my ( $self, $c, $integer ) = @_;
316 $c->stash->{ message } = "Hello ";
317 $c->stash->{ arg_sum } = $integer;
318 }
319
320 # this is our endpoint, because it has no :CaptureArgs
321 sub world : PathPart('world') Chained('hello') Args(1) {
322 my ( $self, $c, $integer ) = @_;
323 $c->stash->{ message } .= "World!";
324 $c->stash->{ arg_sum } += $integer;
325
326 $c->response->body( join "<br/>\n" =>
327 $c->stash->{ message }, $c->stash->{ arg_sum } );
328 }
329
330The debug output provides a separate table for chained actions, showing
67869327 331the whole chain as it would match and the actions it contains. Here's an
332example of the startup output with our actions above:
05a90578 333
334 ...
335 [debug] Loaded Path Part actions:
336 .-----------------------+------------------------------.
337 | Path Spec | Private |
338 +-----------------------+------------------------------+
339 | /hello/*/world/* | /greeting/hello (1) |
340 | | => /greeting/world |
341 '-----------------------+------------------------------'
342 ...
343
67869327 344As you can see, Catalyst only deals with chains as whole paths and
345builds one for each endpoint, which are the actions with C<:Chained> but
346without C<:CaptureArgs>.
05a90578 347
348Let's assume this application gets a request at the path
67869327 349C</hello/23/world/12>. What happens then? First, Catalyst will dispatch
350to the C<hello> action and pass the value C<23> as an argument to it
351after the context. It does so because we have previously used
352C<:CaptureArgs(1)> to declare that it has one path part after itself as
353its argument. We told Catalyst that this is the beginning of the chain
354by specifying C<:Chained('/')>. Also note that instead of saying
355C<:PathPart('hello')> we could also just have said C<:PathPart>, as it
356defaults to the name of the action.
05a90578 357
358After C<hello> has run, Catalyst goes on to dispatch to the C<world>
67869327 359action. This is the last action to be called: Catalyst knows this is an
360endpoint because we did not specify a C<:CaptureArgs>
361attribute. Nevertheless we specify that this action expects an argument,
362but at this point we're using C<:Args(1)> to do that. We could also have
363said C<:Args> or left it out altogether, which would mean this action
364would get all arguments that are there. This action's C<:Chained>
365attribute says C<hello> and tells Catalyst that the C<hello> action in
366the current controller is its parent.
05a90578 367
368With this we have built a chain consisting of two public path parts.
67869327 369C<hello> captures one part of the path as its argument, and also
370specifies the path root as its parent. So this part is
371C</hello/$arg>. The next part is the endpoint C<world>, expecting one
372argument. It sums up to the path part C<world/$arg>. This leads to a
373complete chain of C</hello/$arg/world/$arg> which is matched against the
374requested paths.
375
376This example application would, if run and called by e.g.
377C</hello/23/world/12>, set the stash value C<message> to "Hello" and the
378value C<arg_sum> to "23". The C<world> action would then append "World!"
379to C<message> and add C<12> to the stash's C<arg_sum> value. For the
380sake of simplicity no view is shown. Instead we just put the values of
381the stash into our body. So the output would look like:
05a90578 382
383 Hello World!
384 35
385
67869327 386And our test server would have given us this debugging output for the
05a90578 387request:
388
389 ...
390 [debug] "GET" request for "hello/23/world/12" from "127.0.0.1"
391 [debug] Path is "/greeting/world"
392 [debug] Arguments are "12"
393 [info] Request took 0.164113s (6.093/s)
394 .------------------------------------------+-----------.
395 | Action | Time |
396 +------------------------------------------+-----------+
397 | /greeting/hello | 0.000029s |
398 | /greeting/world | 0.000024s |
399 '------------------------------------------+-----------'
400 ...
401
67869327 402What would be common uses of this dispatch technique? It gives the
403possibility to split up logic that contains steps that each depend on
404each other. An example would be, for example, a wiki path like
05a90578 405C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
406these actions:
407
408 sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
409 my ( $self, $c, $page_name ) = @_;
410 # load the page named $page_name and put the object
411 # into the stash
412 }
413
414 sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
415 my ( $self, $c, $revision_id ) = @_;
67869327 416 # use the page object in the stash to get at its
05a90578 417 # revision with number $revision_id
418 }
419
420 sub view : PathPart Chained('rev') Args(0) {
421 my ( $self, $c ) = @_;
67869327 422 # display the revision in our stash. Another option
05a90578 423 # would be to forward a compatible object to the action
424 # that displays the default wiki pages, unless we want
425 # a different interface here, for example restore
426 # functionality.
427 }
428
67869327 429It would now be possible to add other endpoints, for example C<restore>
430to restore this specific revision as the current state.
05a90578 431
67869327 432You don't have to put all the chained actions in one controller. The
433specification of the parent through C<:Chained> also takes an absolute
434action path as its argument. Just specify it with a leading C</>.
05a90578 435
436If you want, for example, to have actions for the public paths
67869327 437C</foo/12/edit> and C</foo/12>, just specify two actions with
05a90578 438C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
67869327 439path needs a C<:CaptureArgs(1)> attribute and a endpoint with
05a90578 440C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
441the action just a C<:Args(1)> to mark it as endpoint. This sums up to
442this debugging output:
443
444 ...
445 [debug] Loaded Path Part actions:
446 .-----------------------+------------------------------.
447 | Path Spec | Private |
448 +-----------------------+------------------------------+
449 | /foo/* | /controller/foo_view |
450 | /foo/*/edit | /controller/foo_load (1) |
451 | | => /controller/edit |
452 '-----------------------+------------------------------'
453 ...
454
455Here's a more detailed specification of the attributes belonging to
456C<:Chained>:
457
458=head2 Attributes
459
460=over 8
461
462=item PathPart
463
464Sets the name of this part of the chain. If it is specified without
465arguments, it takes the name of the action as default. So basically
466C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
467This can also contain slashes to bind to a deeper level. An action
468with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
469C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
470effect as using C<:PathPart>, it would default to the action name.
471
8f0e79ad 472=item PathPrefix
473
474Sets PathPart to the path_prefix of the current controller.
475
05a90578 476=item Chained
477
478Has to be specified for every child in the chain. Possible values are
c707ce29 479absolute and relative private action paths or a single slash C</> to
480tell Catalyst that this is the root of a chain. The attribute
481C<:Chained> without arguments also defaults to the C</> behavior.
482Relative action paths may use C<../> to refer to actions in parent
483controllers.
05a90578 484
67869327 485Because you can specify an absolute path to the parent action, it
486doesn't matter to Catalyst where that parent is located. So, if your
487design requests it, you can redispatch a chain through any controller or
488namespace you want.
05a90578 489
490Another interesting possibility gives C<:Chained('.')>, which chains
67869327 491itself to an action with the path of the current controller's namespace.
05a90578 492For example:
493
494 # in MyApp::Controller::Foo
495 sub bar : Chained CaptureArgs(1) { ... }
496
497 # in MyApp::Controller::Foo::Bar
498 sub baz : Chained('.') Args(1) { ... }
499
500This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
67869327 501as the argument to Chained here chains the C<baz> action to an action
502with the path of the current controller namespace, namely
503C</foo/bar>. That action chains directly to C</>, so the C</bar/*/baz/*>
504chain comes out as the end product.
05a90578 505
250cad0f 506=item ChainedParent
507
508Chains an action to another action with the same name in the parent
509controller. For Example:
510
511 # in MyApp::Controller::Foo
512 sub bar : Chained CaptureArgs(1) { ... }
513
514 # in MyApp::Controller::Foo::Moo
515 sub bar : ChainedParent Args(1) { ... }
516
517This builds a chain like C</bar/*/bar/*>.
518
05a90578 519=item CaptureArgs
520
67869327 521Must be specified for every part of the chain that is not an
05a90578 522endpoint. With this attribute Catalyst knows how many of the following
67869327 523parts of the path (separated by C</>) this action wants to capture as
524its arguments. If it doesn't expect any, just specify
525C<:CaptureArgs(0)>. The captures get passed to the action's C<@_> right
526after the context, but you can also find them as array references in
05a90578 527C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
528level of the action in the chain that captured the parts of the path.
529
67869327 530An action that is part of a chain (that is, one that has a C<:Chained>
531attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
532as a chain end.
05a90578 533
534=item Args
535
536By default, endpoints receive the rest of the arguments in the path. You
537can tell Catalyst through C<:Args> explicitly how many arguments your
538endpoint expects, just like you can with C<:CaptureArgs>. Note that this
67869327 539also affects whether this chain is invoked on a request. A chain with an
05a90578 540endpoint specifying one argument will only match if exactly one argument
541exists in the path.
542
543You can specify an exact number of arguments like C<:Args(3)>, including
544C<0>. If you just say C<:Args> without any arguments, it is the same as
67869327 545leaving it out altogether: The chain is matched regardless of the number
05a90578 546of path parts after the endpoint.
547
67869327 548Just as with C<:CaptureArgs>, the arguments get passed to the action in
05a90578 549C<@_> after the context object. They can also be reached through
550C<$c-E<gt>request-E<gt>arguments>.
551
552=back
553
67869327 554=head2 Auto actions, dispatching and forwarding
05a90578 555
556Note that the list of C<auto> actions called depends on the private path
67869327 557of the endpoint of the chain, not on the chained actions way. The
558C<auto> actions will be run before the chain dispatching begins. In
559every other aspect, C<auto> actions behave as documented.
05a90578 560
561The C<forward>ing to other actions does just what you would expect. But if
562you C<detach> out of a chain, the rest of the chain will not get called
67869327 563after the C<detach>.
05a90578 564
0bf7ab71 565=head1 AUTHORS
141459fa 566
0bf7ab71 567Catalyst Contributors, see Catalyst.pm
141459fa 568
569=head1 COPYRIGHT
570
571This program is free software, you can redistribute it and/or modify it under
572the same terms as Perl itself.
573
574=cut
575
5761;