remove a confusing and duplicate bit of documentation
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Dispatcher.pm
CommitLineData
68a748b9 1package Catalyst::Dispatcher;
1abd6db7 2
3use strict;
fbcc39ad 4use base 'Class::Accessor::Fast';
a2f2cde9 5use Catalyst::Exception;
f05af9ba 6use Catalyst::Utils;
fbcc39ad 7use Catalyst::Action;
b7aebc12 8use Catalyst::ActionContainer;
b96f127f 9use Catalyst::DispatchType::Default;
bcccee4e 10use Catalyst::DispatchType::Index;
87b85407 11use Text::SimpleTable;
1abd6db7 12use Tree::Simple;
13use Tree::Simple::Visitor::FindByPath;
e72f8f51 14use Scalar::Util ();
1abd6db7 15
fbcc39ad 16# Stringify to class
17use overload '""' => sub { return ref shift }, fallback => 1;
18
49070d25 19__PACKAGE__->mk_accessors(
20 qw/tree dispatch_types registered_dispatch_types
e7bb8d33 21 method_action_class action_container_class
22 preload_dispatch_types postload_dispatch_types
a13e21ab 23 action_hash container_hash
9e81ba44 24 /
49070d25 25);
6d030e6f 26
27# Preload these action types
61a9002d 28our @PRELOAD = qw/Index Path Regex/;
1abd6db7 29
2d1d8f91 30# Postload these action types
61a9002d 31our @POSTLOAD = qw/Default/;
2d1d8f91 32
1abd6db7 33=head1 NAME
34
9c053379 35Catalyst::Dispatcher - The Catalyst Dispatcher
1abd6db7 36
37=head1 SYNOPSIS
38
39See L<Catalyst>.
40
41=head1 DESCRIPTION
42
4ab87e27 43This is the class that maps public urls to actions in your Catalyst
44application based on the attributes you set.
45
1abd6db7 46=head1 METHODS
47
76ddf86b 48=head2 new
4ab87e27 49
50Construct a new dispatcher.
51
e7bb8d33 52=cut
53
54sub new {
9e81ba44 55 my $self = shift;
e7bb8d33 56 my $class = ref($self) || $self;
9e81ba44 57
58 my $obj = $class->SUPER::new(@_);
59
e7bb8d33 60 # set the default pre- and and postloads
9e81ba44 61 $obj->preload_dispatch_types( \@PRELOAD );
e7bb8d33 62 $obj->postload_dispatch_types( \@POSTLOAD );
a13e21ab 63 $obj->action_hash( {} );
64 $obj->container_hash( {} );
65
66 # Create the root node of the tree
67 my $container =
68 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
69 $obj->tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
70
9e81ba44 71 return $obj;
e7bb8d33 72}
73
74=head2 $self->preload_dispatch_types
75
76An arrayref of pre-loaded dispatchtype classes
77
78Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
79To use a custom class outside the regular C<Catalyst> namespace, prefix
80it with a C<+>, like so:
81
82 +My::Dispatch::Type
83
84=head2 $self->postload_dispatch_types
85
86An arrayref of post-loaded dispatchtype classes
87
88Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
89To use a custom class outside the regular C<Catalyst> namespace, prefix
90it with a C<+>, like so:
91
92 +My::Dispatch::Type
93
b5ecfcf0 94=head2 $self->detach( $c, $command [, \@arguments ] )
6ef62eb2 95
4ab87e27 96Documented in L<Catalyst>
97
6ef62eb2 98=cut
99
100sub detach {
fbcc39ad 101 my ( $self, $c, $command, @args ) = @_;
bd7d2e94 102 $c->forward( $command, @args ) if $command;
fbcc39ad 103 die $Catalyst::DETACH;
6ef62eb2 104}
105
b5ecfcf0 106=head2 $self->dispatch($c)
1abd6db7 107
4ab87e27 108Delegate the dispatch to the action that matched the url, or return a
109message about unknown resource
110
111
1abd6db7 112=cut
113
114sub dispatch {
fbcc39ad 115 my ( $self, $c ) = @_;
66e28e3f 116 if ( $c->action ) {
28591cd7 117 $c->forward( join( '/', '', $c->action->namespace, '_DISPATCH' ) );
fbcc39ad 118 }
119
120 else {
1abd6db7 121 my $path = $c->req->path;
122 my $error = $path
123 ? qq/Unknown resource "$path"/
124 : "No default action defined";
125 $c->log->error($error) if $c->debug;
126 $c->error($error);
127 }
128}
129
9bfaf006 130# $self->_command2action( $c, $command [, \@arguments ] )
131# Search for an action, from the command and returns C<($action, $args)> on
132# success. Returns C<(0)> on error.
1abd6db7 133
9bfaf006 134sub _command2action {
e72f8f51 135 my ( $self, $c, $command, @extra_params ) = @_;
99fe1710 136
1abd6db7 137 unless ($command) {
9bfaf006 138 $c->log->debug('Nothing to go to') if $c->debug;
1abd6db7 139 return 0;
140 }
99fe1710 141
e72f8f51 142 my @args;
143
144 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
145 @args = @{ pop @extra_params }
146 } else {
9bfaf006 147 # this is a copy, it may take some abuse from
148 # ->_invoke_as_path if the path had trailing parts
e72f8f51 149 @args = @{ $c->request->arguments };
150 }
151
152 my $action;
153
9bfaf006 154 # go to a string path ("/foo/bar/gorch")
155 # or action object which stringifies to that
e72f8f51 156 $action = $self->_invoke_as_path( $c, "$command", \@args );
99fe1710 157
9bfaf006 158 # go to a component ( "MyApp::*::Foo" or $c->component("...")
159 # - a path or an object)
e72f8f51 160 unless ($action) {
161 my $method = @extra_params ? $extra_params[0] : "process";
162 $action = $self->_invoke_as_component( $c, $command, $method );
163 }
99fe1710 164
9bfaf006 165 return $action, \@args;
166}
167
168=head2 $self->go( $c, $command [, \@arguments ] )
169
170Documented in L<Catalyst>
171
172=cut
173
174sub go {
175 my $self = shift;
176 my ( $c, $command ) = @_;
177 my ( $action, $args ) = $self->_command2action(@_);
99fe1710 178
e540158b 179 unless ($action) {
180 my $error =
9bfaf006 181 qq/Couldn't go to command "$command": /
e540158b 182 . qq/Invalid action or component./;
183 $c->error($error);
184 $c->log->debug($error) if $c->debug;
185 return 0;
186 }
bd7d2e94 187
9bfaf006 188 local $c->request->{arguments} = $args;
189 $c->namespace($action->namespace);
190 $c->action($action);
191 $self->dispatch($c);
192
193 die $Catalyst::GO;
194}
195
196=head2 $self->forward( $c, $command [, \@arguments ] )
197
198Documented in L<Catalyst>
199
200=cut
201
202sub forward {
203 my $self = shift;
204 my ( $c, $command ) = @_;
205 my ( $action, $args ) = $self->_command2action(@_);
206
207 unless ($action) {
208 my $error =
209 qq/Couldn't forward to command "$command": /
210 . qq/Invalid action or component./;
211 $c->error($error);
212 $c->log->debug($error) if $c->debug;
213 return 0;
214 }
adb53907 215
9bfaf006 216 local $c->request->{arguments} = $args;
b8f669f3 217 $action->dispatch( $c );
99fe1710 218
1abd6db7 219 return $c->state;
220}
221
adb53907 222sub _action_rel2abs {
e540158b 223 my ( $self, $c, $path ) = @_;
224
225 unless ( $path =~ m#^/# ) {
226 my $namespace = $c->stack->[-1]->namespace;
227 $path = "$namespace/$path";
228 }
229
230 $path =~ s#^/##;
231 return $path;
adb53907 232}
233
234sub _invoke_as_path {
e540158b 235 my ( $self, $c, $rel_path, $args ) = @_;
236
e540158b 237 my $path = $self->_action_rel2abs( $c, $rel_path );
238
239 my ( $tail, @extra_args );
240 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
241 { # allow $path to be empty
242 if ( my $action = $c->get_action( $tail, $path ) ) {
243 push @$args, @extra_args;
244 return $action;
245 }
246 else {
247 return
248 unless $path
249 ; # if a match on the global namespace failed then the whole lookup failed
250 }
251
252 unshift @extra_args, $tail;
253 }
adb53907 254}
255
256sub _find_component_class {
e540158b 257 my ( $self, $c, $component ) = @_;
adb53907 258
e540158b 259 return ref($component)
260 || ref( $c->component($component) )
261 || $c->component($component);
adb53907 262}
263
264sub _invoke_as_component {
e540158b 265 my ( $self, $c, $component, $method ) = @_;
266
267 my $class = $self->_find_component_class( $c, $component ) || return 0;
e540158b 268
269 if ( my $code = $class->can($method) ) {
270 return $self->method_action_class->new(
271 {
272 name => $method,
273 code => $code,
274 reverse => "$class->$method",
275 class => $class,
276 namespace => Catalyst::Utils::class2prefix(
277 $class, $c->config->{case_sensitive}
278 ),
279 }
280 );
281 }
282 else {
283 my $error =
284 qq/Couldn't forward to "$class". Does not implement "$method"/;
285 $c->error($error);
286 $c->log->debug($error)
287 if $c->debug;
288 return 0;
289 }
adb53907 290}
291
b5ecfcf0 292=head2 $self->prepare_action($c)
fbcc39ad 293
4ab87e27 294Find an dispatch type that matches $c->req->path, and set args from it.
295
fbcc39ad 296=cut
297
298sub prepare_action {
299 my ( $self, $c ) = @_;
300 my $path = $c->req->path;
301 my @path = split /\//, $c->req->path;
302 $c->req->args( \my @args );
303
61a9002d 304 unshift( @path, '' ); # Root action
78d760bb 305
b96f127f 306 DESCEND: while (@path) {
fbcc39ad 307 $path = join '/', @path;
61a9002d 308 $path =~ s#^/##;
fbcc39ad 309
61a9002d 310 $path = '' if $path eq '/'; # Root action
78d760bb 311
22f3a8dd 312 # Check out dispatch types to see if any will handle the path at
313 # this level
314
78d760bb 315 foreach my $type ( @{ $self->dispatch_types } ) {
2633d7dc 316 last DESCEND if $type->match( $c, $path );
66e28e3f 317 }
b96f127f 318
22f3a8dd 319 # If not, move the last part path to args
4082e678 320 my $arg = pop(@path);
321 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
322 unshift @args, $arg;
fbcc39ad 323 }
324
cccc8f68 325 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$c->req->captures||[]};
66d7ad40 326
e3a13771 327 $c->log->debug( 'Path is "' . $c->req->match . '"' )
53119b78 328 if ( $c->debug && length $c->req->match );
e3a13771 329
fbcc39ad 330 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
331 if ( $c->debug && @args );
332}
333
b5ecfcf0 334=head2 $self->get_action( $action, $namespace )
1abd6db7 335
4ab87e27 336returns a named action from a given namespace.
337
1abd6db7 338=cut
339
340sub get_action {
bcd1002b 341 my ( $self, $name, $namespace ) = @_;
79a3189a 342 return unless $name;
3d0d6d21 343
53119b78 344 $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
99fe1710 345
a13e21ab 346 return $self->action_hash->{"$namespace/$name"};
1abd6db7 347}
348
34d28dfd 349=head2 $self->get_action_by_path( $path );
350
351Returns the named action by its full path.
3d0d6d21 352
34d28dfd 353=cut
3d0d6d21 354
355sub get_action_by_path {
356 my ( $self, $path ) = @_;
ea0e58d9 357 $path =~ s/^\///;
28928de9 358 $path = "/$path" unless $path =~ /\//;
3d0d6d21 359 $self->action_hash->{$path};
360}
361
b5ecfcf0 362=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 363
364=cut
365
366sub get_actions {
367 my ( $self, $c, $action, $namespace ) = @_;
368 return [] unless $action;
3d0d6d21 369
28928de9 370 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 371
372 my @match = $self->get_containers($namespace);
373
684d10ed 374 return map { $_->get_action($action) } @match;
a9dc674c 375}
376
b5ecfcf0 377=head2 $self->get_containers( $namespace )
cfd04b0c 378
4ab87e27 379Return all the action containers for a given namespace, inclusive
380
cfd04b0c 381=cut
382
383sub get_containers {
384 my ( $self, $namespace ) = @_;
a13e21ab 385 $namespace ||= '';
386 $namespace = '' if $namespace eq '/';
cfd04b0c 387
a13e21ab 388 my @containers;
cfd04b0c 389
7f23827b 390 if ( length $namespace ) {
391 do {
392 push @containers, $self->container_hash->{$namespace};
393 } while ( $namespace =~ s#/[^/]+$## );
394 }
90ce41ba 395
a13e21ab 396 return reverse grep { defined } @containers, $self->container_hash->{''};
90ce41ba 397
a13e21ab 398 my @parts = split '/', $namespace;
cfd04b0c 399}
400
ea0e58d9 401=head2 $self->uri_for_action($action, \@captures)
402
403Takes a Catalyst::Action object and action parameters and returns a URI
404part such that if $c->req->path were this URI part, this action would be
405dispatched to with $c->req->captures set to the supplied arrayref.
406
407If the action object is not available for external dispatch or the dispatcher
408cannot determine an appropriate URI, this method will return undef.
409
410=cut
411
412sub uri_for_action {
413 my ( $self, $action, $captures) = @_;
414 $captures ||= [];
415 foreach my $dispatch_type ( @{ $self->dispatch_types } ) {
416 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 417 return( $uri eq '' ? '/' : $uri )
418 if defined($uri);
ea0e58d9 419 }
420 return undef;
421}
422
b5ecfcf0 423=head2 $self->register( $c, $action )
aad72cc9 424
4ab87e27 425Make sure all required dispatch types for this action are loaded, then
426pass the action to our dispatch types so they can register it if required.
427Also, set up the tree with the action containers.
428
aad72cc9 429=cut
430
79a3189a 431sub register {
432 my ( $self, $c, $action ) = @_;
433
694d15f1 434 my $registered = $self->registered_dispatch_types;
435
436 my $priv = 0;
437 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 438 next if $key eq 'Private';
694d15f1 439 my $class = "Catalyst::DispatchType::$key";
440 unless ( $registered->{$class} ) {
441 eval "require $class";
442 push( @{ $self->dispatch_types }, $class->new ) unless $@;
443 $registered->{$class} = 1;
444 }
445 }
446
447 # Pass the action to our dispatch types so they can register it if reqd.
01ce0928 448 foreach my $type ( @{ $self->dispatch_types } ) {
9a6ecf4f 449 $type->register( $c, $action );
694d15f1 450 }
451
79a3189a 452 my $namespace = $action->namespace;
a13e21ab 453 my $name = $action->name;
c7116517 454
ad5e4650 455 my $container = $self->_find_or_create_action_container($namespace);
15e9b5dd 456
457 # Set the method value
a13e21ab 458 $container->add_action($action);
c7116517 459
a13e21ab 460 $self->action_hash->{"$namespace/$name"} = $action;
461 $self->container_hash->{$namespace} = $container;
15e9b5dd 462}
463
ad5e4650 464sub _find_or_create_action_container {
a13e21ab 465 my ( $self, $namespace ) = @_;
466
467 my $tree ||= $self->tree;
99fe1710 468
a13e21ab 469 return $tree->getNodeValue unless $namespace;
78d760bb 470
a13e21ab 471 my @namespace = split '/', $namespace;
472 return $self->_find_or_create_namespace_node( $tree, @namespace )
473 ->getNodeValue;
8505565b 474}
90ce41ba 475
8505565b 476sub _find_or_create_namespace_node {
a13e21ab 477 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 478
a13e21ab 479 return $parent unless $part;
8505565b 480
a13e21ab 481 my $child =
482 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 483
a13e21ab 484 unless ($child) {
485 my $container = Catalyst::ActionContainer->new($part);
486 $parent->addChild( $child = Tree::Simple->new($container) );
487 }
99fe1710 488
a13e21ab 489 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 490}
491
4ab87e27 492=head2 $self->setup_actions( $class, $context )
493
1abd6db7 494
495=cut
496
497sub setup_actions {
11bd4e3e 498 my ( $self, $c ) = @_;
99fe1710 499
6d030e6f 500 $self->dispatch_types( [] );
91d4abc5 501 $self->registered_dispatch_types( {} );
49070d25 502 $self->method_action_class('Catalyst::Action');
503 $self->action_container_class('Catalyst::ActionContainer');
12e28165 504
9e81ba44 505 my @classes =
ad5e4650 506 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
9e81ba44 507 @{ $self->registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 508
49070d25 509 foreach my $comp ( values %{ $c->components } ) {
510 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 511 }
e494bd6b 512
ad5e4650 513 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 514
11bd4e3e 515 return unless $c->debug;
99fe1710 516
684d10ed 517 my $privates = Text::SimpleTable->new(
dbf03873 518 [ 20, 'Private' ],
34d28dfd 519 [ 36, 'Class' ],
dbf03873 520 [ 12, 'Method' ]
684d10ed 521 );
99fe1710 522
87b85407 523 my $has_private = 0;
1abd6db7 524 my $walker = sub {
525 my ( $walker, $parent, $prefix ) = @_;
526 $prefix .= $parent->getNodeValue || '';
527 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 528 my $node = $parent->getNodeValue->actions;
99fe1710 529
78d760bb 530 for my $action ( keys %{$node} ) {
b7aebc12 531 my $action_obj = $node->{$action};
b0bb11ec 532 next
533 if ( ( $action =~ /^_.*/ )
534 && ( !$c->config->{show_internal_actions} ) );
684d10ed 535 $privates->row( "$prefix$action", $action_obj->class, $action );
87b85407 536 $has_private = 1;
1abd6db7 537 }
99fe1710 538
1abd6db7 539 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
540 };
99fe1710 541
1abd6db7 542 $walker->( $walker, $self->tree, '' );
1cf0345b 543 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
544 if $has_private;
99fe1710 545
a9cbd748 546 # List all public actions
11bd4e3e 547 $_->list($c) for @{ $self->dispatch_types };
1abd6db7 548}
549
ad5e4650 550sub _load_dispatch_types {
9e81ba44 551 my ( $self, @types ) = @_;
552
553 my @loaded;
554
555 # Preload action types
556 for my $type (@types) {
557 my $class =
558 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
559 eval "require $class";
560 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
561 if $@;
562 push @{ $self->dispatch_types }, $class->new;
563
564 push @loaded, $class;
565 }
566
a13e21ab 567 return @loaded;
9e81ba44 568}
569
1abd6db7 570=head1 AUTHOR
571
572Sebastian Riedel, C<sri@cpan.org>
158c88c0 573Matt S Trout, C<mst@shadowcatsystems.co.uk>
1abd6db7 574
575=head1 COPYRIGHT
576
577This program is free software, you can redistribute it and/or modify it under
578the same terms as Perl itself.
579
580=cut
581
5821;