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