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