1 package Catalyst::DispatchType::Chained;
4 extends 'Catalyst::DispatchType';
7 #use base qw/Catalyst::DispatchType/;
9 use Catalyst::ActionChain;
33 # please don't perltidy this. hairy code within.
37 Catalyst::DispatchType::Chained - Path Part DispatchType
41 # root action - captures one argument after it
42 sub foo_setup : Chained('/') PathPart('foo') CaptureArgs(1) {
43 my ( $self, $c, $foo_arg ) = @_;
47 # child action endpoint - takes one argument
48 sub bar : Chained('foo_setup') Args(1) {
49 my ( $self, $c, $bar_arg ) = @_;
59 =head2 $self->list($c)
61 Debug output for Path Part dispatch points
66 my ( $self, $c ) = @_;
68 return unless $self->_endpoints;
70 my $paths = Text::SimpleTable->new(
71 [ 35, 'Path Spec' ], [ 36, 'Private' ]
74 ENDPOINT: foreach my $endpoint (
75 sort { $a->reverse cmp $b->reverse }
76 @{ $self->_endpoints }
78 my $args = $endpoint->attributes->{Args}->[0];
79 my @parts = (defined($args) ? (("*") x $args) : '...');
84 if (my $cap = $curr->attributes->{CaptureArgs}) {
85 unshift(@parts, (("*") x $cap->[0]));
87 if (my $pp = $curr->attributes->{PartPath}) {
88 unshift(@parts, $pp->[0])
89 if (defined $pp->[0] && length $pp->[0]);
91 $parent = $curr->attributes->{Chained}->[0];
92 $curr = $self->_actions->{$parent};
93 unshift(@parents, $curr) if $curr;
95 next ENDPOINT unless $parent eq '/'; # skip dangling action
97 foreach my $p (@parents) {
99 if (my $cap = $p->attributes->{CaptureArgs}) {
100 $name .= ' ('.$cap->[0].')';
102 unless ($p eq $parents[0]) {
103 $name = "-> ${name}";
105 push(@rows, [ '', $name ]);
107 push(@rows, [ '', (@rows ? "=> " : '')."/${endpoint}" ]);
108 $rows[0][0] = join('/', '', @parts);
109 $paths->row(@$_) for @rows;
112 $c->log->debug( "Loaded Chained actions:\n" . $paths->draw . "\n" );
115 =head2 $self->match( $c, $path )
117 Calls C<recurse_match> to see if a chain matches the C<$path>.
122 my ( $self, $c, $path ) = @_;
124 return 0 if @{$c->req->args};
126 my @parts = split('/', $path);
128 my ($chain, $captures, $parts) = $self->recurse_match($c, '/', \@parts);
129 push @{$c->req->args}, @$parts if $parts && @$parts;
131 return 0 unless $chain;
133 my $action = Catalyst::ActionChain->from_chain($chain);
135 $c->req->action("/${action}");
136 $c->req->match("/${action}");
137 $c->req->captures($captures);
139 $c->namespace( $action->namespace );
144 =head2 $self->recurse_match( $c, $parent, \@path_parts )
146 Recursive search for a matching chain.
151 my ( $self, $c, $parent, $path_parts ) = @_;
152 my $children = $self->_children_of->{$parent};
153 return () unless $children;
156 TRY: foreach my $try_part (sort { length($b) <=> length($a) }
158 # $b then $a to try longest part first
159 my @parts = @$path_parts;
160 if (length $try_part) { # test and strip PathPart
162 ($try_part eq join('/', # assemble equal number of parts
163 splice( # and strip them off @parts as well
164 @parts, 0, scalar(@{[split('/', $try_part)]})
165 ))); # @{[]} to avoid split to @_
167 my @try_actions = @{$children->{$try_part}};
168 TRY_ACTION: foreach my $action (@try_actions) {
169 if (my $capture_attr = $action->attributes->{CaptureArgs}) {
171 # Short-circuit if not enough remaining parts
172 next TRY_ACTION unless @parts >= $capture_attr->[0];
175 my @parts = @parts; # localise
177 # strip CaptureArgs into list
178 push(@captures, splice(@parts, 0, $capture_attr->[0]));
180 # try the remaining parts against children of this action
181 my ($actions, $captures, $action_parts) = $self->recurse_match(
182 $c, '/'.$action->reverse, \@parts
184 if ($actions && (!$best_action || $#$action_parts < $#{$best_action->{parts}})){
186 actions => [ $action, @$actions ],
187 captures=> [ @captures, @$captures ],
188 parts => $action_parts
194 local $c->req->{arguments} = [ @{$c->req->args}, @parts ];
195 next TRY_ACTION unless $action->match($c);
197 my $args_attr = $action->attributes->{Args}->[0];
199 # No best action currently
200 # OR This one matches with fewer parts left than the current best action,
201 # And therefore is a better match
202 # OR No parts and this expects 0
203 # The current best action might also be Args(0),
204 # but we couldn't chose between then anyway so we'll take the last seen
207 @parts < @{$best_action->{parts}} ||
208 (!@parts && $args_attr eq 0)){
210 actions => [ $action ],
218 return @$best_action{qw/actions captures parts/} if $best_action;
222 =head2 $self->register( $c, $action )
224 Calls register_path for every Path attribute for the given $action.
229 my ( $self, $c, $action ) = @_;
231 my @chained_attr = @{ $action->attributes->{Chained} || [] };
233 return 0 unless @chained_attr;
235 if (@chained_attr > 2) {
236 Catalyst::Exception->throw(
237 "Multiple Chained attributes not supported registering ${action}"
241 my $parent = $chained_attr[0];
243 if (defined($parent) && length($parent)) {
244 if ($parent eq '.') {
245 $parent = '/'.$action->namespace;
246 } elsif ($parent !~ m/^\//) {
247 if ($action->namespace) {
248 $parent = '/'.join('/', $action->namespace, $parent);
250 $parent = '/'.$parent; # special case namespace '' (root)
257 $action->attributes->{Chained} = [ $parent ];
259 my $children = ($self->_children_of->{$parent} ||= {});
261 my @path_part = @{ $action->attributes->{PathPart} || [] };
263 my $part = $action->name;
265 if (@path_part == 1 && defined $path_part[0]) {
266 $part = $path_part[0];
267 } elsif (@path_part > 1) {
268 Catalyst::Exception->throw(
269 "Multiple PathPart attributes not supported registering ${action}"
273 if ($part =~ m(^/)) {
274 Catalyst::Exception->throw(
275 "Absolute parameters to PathPart not allowed registering ${action}"
279 $action->attributes->{PartPath} = [ $part ];
281 unshift(@{ $children->{$part} ||= [] }, $action);
283 $self->_actions->{'/'.$action->reverse} = $action;
285 unless ($action->attributes->{CaptureArgs}) {
286 unshift(@{ $self->_endpoints }, $action);
292 =head2 $self->uri_for_action($action, $captures)
294 Get the URI part for the action, using C<$captures> to fill
300 my ( $self, $action, $captures ) = @_;
302 return undef unless ($action->attributes->{Chained}
303 && !$action->attributes->{CaptureArgs});
306 my @captures = @$captures;
307 my $parent = "DUMMY";
310 if (my $cap = $curr->attributes->{CaptureArgs}) {
311 return undef unless @captures >= $cap->[0]; # not enough captures
313 unshift(@parts, splice(@captures, -$cap->[0]));
316 if (my $pp = $curr->attributes->{PartPath}) {
317 unshift(@parts, $pp->[0])
318 if (defined($pp->[0]) && length($pp->[0]));
320 $parent = $curr->attributes->{Chained}->[0];
321 $curr = $self->_actions->{$parent};
324 return undef unless $parent eq '/'; # fail for dangling action
326 return undef if @captures; # fail for too many captures
328 return join('/', '', @parts);
336 The C<Chained> attribute allows you to chain public path parts together
337 by their private names. A chain part's path can be specified with
338 C<PathPart> and can be declared to expect an arbitrary number of
339 arguments. The endpoint of the chain specifies how many arguments it
340 gets through the C<Args> attribute. C<:Args(0)> would be none at all,
341 C<:Args> without an integer would be unlimited. The path parts that
342 aren't endpoints are using C<CaptureArgs> to specify how many parameters
343 they expect to receive. As an example setup:
345 package MyApp::Controller::Greeting;
346 use base qw/ Catalyst::Controller /;
348 # this is the beginning of our chain
349 sub hello : PathPart('hello') Chained('/') CaptureArgs(1) {
350 my ( $self, $c, $integer ) = @_;
351 $c->stash->{ message } = "Hello ";
352 $c->stash->{ arg_sum } = $integer;
355 # this is our endpoint, because it has no :CaptureArgs
356 sub world : PathPart('world') Chained('hello') Args(1) {
357 my ( $self, $c, $integer ) = @_;
358 $c->stash->{ message } .= "World!";
359 $c->stash->{ arg_sum } += $integer;
361 $c->response->body( join "<br/>\n" =>
362 $c->stash->{ message }, $c->stash->{ arg_sum } );
365 The debug output provides a separate table for chained actions, showing
366 the whole chain as it would match and the actions it contains. Here's an
367 example of the startup output with our actions above:
370 [debug] Loaded Path Part actions:
371 .-----------------------+------------------------------.
372 | Path Spec | Private |
373 +-----------------------+------------------------------+
374 | /hello/*/world/* | /greeting/hello (1) |
375 | | => /greeting/world |
376 '-----------------------+------------------------------'
379 As you can see, Catalyst only deals with chains as whole paths and
380 builds one for each endpoint, which are the actions with C<:Chained> but
381 without C<:CaptureArgs>.
383 Let's assume this application gets a request at the path
384 C</hello/23/world/12>. What happens then? First, Catalyst will dispatch
385 to the C<hello> action and pass the value C<23> as an argument to it
386 after the context. It does so because we have previously used
387 C<:CaptureArgs(1)> to declare that it has one path part after itself as
388 its argument. We told Catalyst that this is the beginning of the chain
389 by specifying C<:Chained('/')>. Also note that instead of saying
390 C<:PathPart('hello')> we could also just have said C<:PathPart>, as it
391 defaults to the name of the action.
393 After C<hello> has run, Catalyst goes on to dispatch to the C<world>
394 action. This is the last action to be called: Catalyst knows this is an
395 endpoint because we did not specify a C<:CaptureArgs>
396 attribute. Nevertheless we specify that this action expects an argument,
397 but at this point we're using C<:Args(1)> to do that. We could also have
398 said C<:Args> or left it out altogether, which would mean this action
399 would get all arguments that are there. This action's C<:Chained>
400 attribute says C<hello> and tells Catalyst that the C<hello> action in
401 the current controller is its parent.
403 With this we have built a chain consisting of two public path parts.
404 C<hello> captures one part of the path as its argument, and also
405 specifies the path root as its parent. So this part is
406 C</hello/$arg>. The next part is the endpoint C<world>, expecting one
407 argument. It sums up to the path part C<world/$arg>. This leads to a
408 complete chain of C</hello/$arg/world/$arg> which is matched against the
411 This example application would, if run and called by e.g.
412 C</hello/23/world/12>, set the stash value C<message> to "Hello" and the
413 value C<arg_sum> to "23". The C<world> action would then append "World!"
414 to C<message> and add C<12> to the stash's C<arg_sum> value. For the
415 sake of simplicity no view is shown. Instead we just put the values of
416 the stash into our body. So the output would look like:
421 And our test server would have given us this debugging output for the
425 [debug] "GET" request for "hello/23/world/12" from "127.0.0.1"
426 [debug] Path is "/greeting/world"
427 [debug] Arguments are "12"
428 [info] Request took 0.164113s (6.093/s)
429 .------------------------------------------+-----------.
431 +------------------------------------------+-----------+
432 | /greeting/hello | 0.000029s |
433 | /greeting/world | 0.000024s |
434 '------------------------------------------+-----------'
437 What would be common uses of this dispatch technique? It gives the
438 possibility to split up logic that contains steps that each depend on
439 each other. An example would be, for example, a wiki path like
440 C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
443 sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
444 my ( $self, $c, $page_name ) = @_;
445 # load the page named $page_name and put the object
449 sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
450 my ( $self, $c, $revision_id ) = @_;
451 # use the page object in the stash to get at its
452 # revision with number $revision_id
455 sub view : PathPart Chained('rev') Args(0) {
456 my ( $self, $c ) = @_;
457 # display the revision in our stash. Another option
458 # would be to forward a compatible object to the action
459 # that displays the default wiki pages, unless we want
460 # a different interface here, for example restore
464 It would now be possible to add other endpoints, for example C<restore>
465 to restore this specific revision as the current state.
467 You don't have to put all the chained actions in one controller. The
468 specification of the parent through C<:Chained> also takes an absolute
469 action path as its argument. Just specify it with a leading C</>.
471 If you want, for example, to have actions for the public paths
472 C</foo/12/edit> and C</foo/12>, just specify two actions with
473 C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
474 path needs a C<:CaptureArgs(1)> attribute and a endpoint with
475 C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
476 the action just a C<:Args(1)> to mark it as endpoint. This sums up to
477 this debugging output:
480 [debug] Loaded Path Part actions:
481 .-----------------------+------------------------------.
482 | Path Spec | Private |
483 +-----------------------+------------------------------+
484 | /foo/* | /controller/foo_view |
485 | /foo/*/edit | /controller/foo_load (1) |
486 | | => /controller/edit |
487 '-----------------------+------------------------------'
490 Here's a more detailed specification of the attributes belonging to
499 Sets the name of this part of the chain. If it is specified without
500 arguments, it takes the name of the action as default. So basically
501 C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
502 This can also contain slashes to bind to a deeper level. An action
503 with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
504 C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
505 effect as using C<:PathPart>, it would default to the action name.
509 Has to be specified for every child in the chain. Possible values are
510 absolute and relative private action paths, with the relatives pointing
511 to the current controller, or a single slash C</> to tell Catalyst that
512 this is the root of a chain. The attribute C<:Chained> without arguments
513 also defaults to the C</> behavior.
515 Because you can specify an absolute path to the parent action, it
516 doesn't matter to Catalyst where that parent is located. So, if your
517 design requests it, you can redispatch a chain through any controller or
520 Another interesting possibility gives C<:Chained('.')>, which chains
521 itself to an action with the path of the current controller's namespace.
524 # in MyApp::Controller::Foo
525 sub bar : Chained CaptureArgs(1) { ... }
527 # in MyApp::Controller::Foo::Bar
528 sub baz : Chained('.') Args(1) { ... }
530 This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
531 as the argument to Chained here chains the C<baz> action to an action
532 with the path of the current controller namespace, namely
533 C</foo/bar>. That action chains directly to C</>, so the C</bar/*/baz/*>
534 chain comes out as the end product.
538 Must be specified for every part of the chain that is not an
539 endpoint. With this attribute Catalyst knows how many of the following
540 parts of the path (separated by C</>) this action wants to capture as
541 its arguments. If it doesn't expect any, just specify
542 C<:CaptureArgs(0)>. The captures get passed to the action's C<@_> right
543 after the context, but you can also find them as array references in
544 C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
545 level of the action in the chain that captured the parts of the path.
547 An action that is part of a chain (that is, one that has a C<:Chained>
548 attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
553 By default, endpoints receive the rest of the arguments in the path. You
554 can tell Catalyst through C<:Args> explicitly how many arguments your
555 endpoint expects, just like you can with C<:CaptureArgs>. Note that this
556 also affects whether this chain is invoked on a request. A chain with an
557 endpoint specifying one argument will only match if exactly one argument
560 You can specify an exact number of arguments like C<:Args(3)>, including
561 C<0>. If you just say C<:Args> without any arguments, it is the same as
562 leaving it out altogether: The chain is matched regardless of the number
563 of path parts after the endpoint.
565 Just as with C<:CaptureArgs>, the arguments get passed to the action in
566 C<@_> after the context object. They can also be reached through
567 C<$c-E<gt>request-E<gt>arguments>.
571 =head2 Auto actions, dispatching and forwarding
573 Note that the list of C<auto> actions called depends on the private path
574 of the endpoint of the chain, not on the chained actions way. The
575 C<auto> actions will be run before the chain dispatching begins. In
576 every other aspect, C<auto> actions behave as documented.
578 The C<forward>ing to other actions does just what you would expect. But if
579 you C<detach> out of a chain, the rest of the chain will not get called
584 Matt S Trout <mst@shadowcatsystems.co.uk>
588 This program is free software, you can redistribute it and/or modify it under
589 the same terms as Perl itself.