Up Module install dep, update timestamp
[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
f9bcc128 297=head2 $c->expand_action($action)
298
299Return a list of actions that represents a chained action. See
300L<Catalyst::Dispatcher> for more info. You probably want to
301use the expand_action it provides rather than this directly.
302
303=cut
304
305sub expand_action {
306 my ($self, $action) = @_;
307
308 return unless $action->attributes && $action->attributes->{Chained};
309
310 my @chain;
311 my $curr = $action;
312
313 while ($curr) {
314 push @chain, $curr;
315 my $parent = $curr->attributes->{Chained}->[0];
316 $curr = $self->{'actions'}{$parent};
317 }
318
319 return Catalyst::ActionChain->from_chain([reverse @chain]);
320}
321
05a90578 322=head1 USAGE
323
324=head2 Introduction
325
326The C<Chained> attribute allows you to chain public path parts together
67869327 327by their private names. A chain part's path can be specified with
328C<PathPart> and can be declared to expect an arbitrary number of
329arguments. The endpoint of the chain specifies how many arguments it
330gets through the C<Args> attribute. C<:Args(0)> would be none at all,
331C<:Args> without an integer would be unlimited. The path parts that
332aren't endpoints are using C<CaptureArgs> to specify how many parameters
333they expect to receive. As an example setup:
05a90578 334
335 package MyApp::Controller::Greeting;
336 use base qw/ Catalyst::Controller /;
337
338 # this is the beginning of our chain
339 sub hello : PathPart('hello') Chained('/') CaptureArgs(1) {
340 my ( $self, $c, $integer ) = @_;
341 $c->stash->{ message } = "Hello ";
342 $c->stash->{ arg_sum } = $integer;
343 }
344
345 # this is our endpoint, because it has no :CaptureArgs
346 sub world : PathPart('world') Chained('hello') Args(1) {
347 my ( $self, $c, $integer ) = @_;
348 $c->stash->{ message } .= "World!";
349 $c->stash->{ arg_sum } += $integer;
350
351 $c->response->body( join "<br/>\n" =>
352 $c->stash->{ message }, $c->stash->{ arg_sum } );
353 }
354
355The debug output provides a separate table for chained actions, showing
67869327 356the whole chain as it would match and the actions it contains. Here's an
357example of the startup output with our actions above:
05a90578 358
359 ...
360 [debug] Loaded Path Part actions:
361 .-----------------------+------------------------------.
362 | Path Spec | Private |
363 +-----------------------+------------------------------+
364 | /hello/*/world/* | /greeting/hello (1) |
365 | | => /greeting/world |
366 '-----------------------+------------------------------'
367 ...
368
67869327 369As you can see, Catalyst only deals with chains as whole paths and
370builds one for each endpoint, which are the actions with C<:Chained> but
371without C<:CaptureArgs>.
05a90578 372
373Let's assume this application gets a request at the path
67869327 374C</hello/23/world/12>. What happens then? First, Catalyst will dispatch
375to the C<hello> action and pass the value C<23> as an argument to it
376after the context. It does so because we have previously used
377C<:CaptureArgs(1)> to declare that it has one path part after itself as
378its argument. We told Catalyst that this is the beginning of the chain
379by specifying C<:Chained('/')>. Also note that instead of saying
380C<:PathPart('hello')> we could also just have said C<:PathPart>, as it
381defaults to the name of the action.
05a90578 382
383After C<hello> has run, Catalyst goes on to dispatch to the C<world>
67869327 384action. This is the last action to be called: Catalyst knows this is an
385endpoint because we did not specify a C<:CaptureArgs>
386attribute. Nevertheless we specify that this action expects an argument,
387but at this point we're using C<:Args(1)> to do that. We could also have
388said C<:Args> or left it out altogether, which would mean this action
389would get all arguments that are there. This action's C<:Chained>
390attribute says C<hello> and tells Catalyst that the C<hello> action in
391the current controller is its parent.
05a90578 392
393With this we have built a chain consisting of two public path parts.
67869327 394C<hello> captures one part of the path as its argument, and also
395specifies the path root as its parent. So this part is
396C</hello/$arg>. The next part is the endpoint C<world>, expecting one
397argument. It sums up to the path part C<world/$arg>. This leads to a
398complete chain of C</hello/$arg/world/$arg> which is matched against the
399requested paths.
400
401This example application would, if run and called by e.g.
402C</hello/23/world/12>, set the stash value C<message> to "Hello" and the
403value C<arg_sum> to "23". The C<world> action would then append "World!"
404to C<message> and add C<12> to the stash's C<arg_sum> value. For the
405sake of simplicity no view is shown. Instead we just put the values of
406the stash into our body. So the output would look like:
05a90578 407
408 Hello World!
409 35
410
67869327 411And our test server would have given us this debugging output for the
05a90578 412request:
413
414 ...
415 [debug] "GET" request for "hello/23/world/12" from "127.0.0.1"
416 [debug] Path is "/greeting/world"
417 [debug] Arguments are "12"
418 [info] Request took 0.164113s (6.093/s)
419 .------------------------------------------+-----------.
420 | Action | Time |
421 +------------------------------------------+-----------+
422 | /greeting/hello | 0.000029s |
423 | /greeting/world | 0.000024s |
424 '------------------------------------------+-----------'
425 ...
426
67869327 427What would be common uses of this dispatch technique? It gives the
428possibility to split up logic that contains steps that each depend on
429each other. An example would be, for example, a wiki path like
05a90578 430C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
431these actions:
432
433 sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
434 my ( $self, $c, $page_name ) = @_;
435 # load the page named $page_name and put the object
436 # into the stash
437 }
438
439 sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
440 my ( $self, $c, $revision_id ) = @_;
67869327 441 # use the page object in the stash to get at its
05a90578 442 # revision with number $revision_id
443 }
444
445 sub view : PathPart Chained('rev') Args(0) {
446 my ( $self, $c ) = @_;
67869327 447 # display the revision in our stash. Another option
05a90578 448 # would be to forward a compatible object to the action
449 # that displays the default wiki pages, unless we want
450 # a different interface here, for example restore
451 # functionality.
452 }
453
67869327 454It would now be possible to add other endpoints, for example C<restore>
455to restore this specific revision as the current state.
05a90578 456
67869327 457You don't have to put all the chained actions in one controller. The
458specification of the parent through C<:Chained> also takes an absolute
459action path as its argument. Just specify it with a leading C</>.
05a90578 460
461If you want, for example, to have actions for the public paths
67869327 462C</foo/12/edit> and C</foo/12>, just specify two actions with
05a90578 463C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
67869327 464path needs a C<:CaptureArgs(1)> attribute and a endpoint with
05a90578 465C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
466the action just a C<:Args(1)> to mark it as endpoint. This sums up to
467this debugging output:
468
469 ...
470 [debug] Loaded Path Part actions:
471 .-----------------------+------------------------------.
472 | Path Spec | Private |
473 +-----------------------+------------------------------+
474 | /foo/* | /controller/foo_view |
475 | /foo/*/edit | /controller/foo_load (1) |
476 | | => /controller/edit |
477 '-----------------------+------------------------------'
478 ...
479
480Here's a more detailed specification of the attributes belonging to
481C<:Chained>:
482
483=head2 Attributes
484
485=over 8
486
487=item PathPart
488
489Sets the name of this part of the chain. If it is specified without
490arguments, it takes the name of the action as default. So basically
491C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
492This can also contain slashes to bind to a deeper level. An action
493with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
494C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
495effect as using C<:PathPart>, it would default to the action name.
496
8f0e79ad 497=item PathPrefix
498
499Sets PathPart to the path_prefix of the current controller.
500
05a90578 501=item Chained
502
503Has to be specified for every child in the chain. Possible values are
c707ce29 504absolute and relative private action paths or a single slash C</> to
505tell Catalyst that this is the root of a chain. The attribute
506C<:Chained> without arguments also defaults to the C</> behavior.
507Relative action paths may use C<../> to refer to actions in parent
508controllers.
05a90578 509
67869327 510Because you can specify an absolute path to the parent action, it
511doesn't matter to Catalyst where that parent is located. So, if your
512design requests it, you can redispatch a chain through any controller or
513namespace you want.
05a90578 514
515Another interesting possibility gives C<:Chained('.')>, which chains
67869327 516itself to an action with the path of the current controller's namespace.
05a90578 517For example:
518
519 # in MyApp::Controller::Foo
520 sub bar : Chained CaptureArgs(1) { ... }
521
522 # in MyApp::Controller::Foo::Bar
523 sub baz : Chained('.') Args(1) { ... }
524
525This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
67869327 526as the argument to Chained here chains the C<baz> action to an action
527with the path of the current controller namespace, namely
528C</foo/bar>. That action chains directly to C</>, so the C</bar/*/baz/*>
529chain comes out as the end product.
05a90578 530
250cad0f 531=item ChainedParent
532
533Chains an action to another action with the same name in the parent
534controller. For Example:
535
536 # in MyApp::Controller::Foo
537 sub bar : Chained CaptureArgs(1) { ... }
538
539 # in MyApp::Controller::Foo::Moo
540 sub bar : ChainedParent Args(1) { ... }
541
542This builds a chain like C</bar/*/bar/*>.
543
05a90578 544=item CaptureArgs
545
67869327 546Must be specified for every part of the chain that is not an
05a90578 547endpoint. With this attribute Catalyst knows how many of the following
67869327 548parts of the path (separated by C</>) this action wants to capture as
549its arguments. If it doesn't expect any, just specify
550C<:CaptureArgs(0)>. The captures get passed to the action's C<@_> right
551after the context, but you can also find them as array references in
05a90578 552C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
553level of the action in the chain that captured the parts of the path.
554
67869327 555An action that is part of a chain (that is, one that has a C<:Chained>
556attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
557as a chain end.
05a90578 558
559=item Args
560
561By default, endpoints receive the rest of the arguments in the path. You
562can tell Catalyst through C<:Args> explicitly how many arguments your
563endpoint expects, just like you can with C<:CaptureArgs>. Note that this
67869327 564also affects whether this chain is invoked on a request. A chain with an
05a90578 565endpoint specifying one argument will only match if exactly one argument
566exists in the path.
567
568You can specify an exact number of arguments like C<:Args(3)>, including
569C<0>. If you just say C<:Args> without any arguments, it is the same as
67869327 570leaving it out altogether: The chain is matched regardless of the number
05a90578 571of path parts after the endpoint.
572
67869327 573Just as with C<:CaptureArgs>, the arguments get passed to the action in
05a90578 574C<@_> after the context object. They can also be reached through
575C<$c-E<gt>request-E<gt>arguments>.
576
577=back
578
67869327 579=head2 Auto actions, dispatching and forwarding
05a90578 580
581Note that the list of C<auto> actions called depends on the private path
67869327 582of the endpoint of the chain, not on the chained actions way. The
583C<auto> actions will be run before the chain dispatching begins. In
584every other aspect, C<auto> actions behave as documented.
05a90578 585
586The C<forward>ing to other actions does just what you would expect. But if
587you C<detach> out of a chain, the rest of the chain will not get called
67869327 588after the C<detach>.
05a90578 589
0bf7ab71 590=head1 AUTHORS
141459fa 591
0bf7ab71 592Catalyst Contributors, see Catalyst.pm
141459fa 593
594=head1 COPYRIGHT
595
596This program is free software, you can redistribute it and/or modify it under
597the same terms as Perl itself.
598
599=cut
600
6011;