1 package Catalyst::Dispatcher;
4 use base 'Class::Accessor::Fast';
5 use Catalyst::Exception;
8 use Catalyst::ActionContainer;
9 use Catalyst::DispatchType::Default;
10 use Catalyst::DispatchType::Index;
12 use Text::SimpleTable;
14 use Tree::Simple::Visitor::FindByPath;
18 use overload '""' => sub { return ref shift }, fallback => 1;
20 __PACKAGE__->mk_accessors(
21 qw/tree dispatch_types registered_dispatch_types
22 method_action_class action_container_class
23 preload_dispatch_types postload_dispatch_types
24 action_hash container_hash
28 # Preload these action types
29 our @PRELOAD = qw/Index Path Regex/;
31 # Postload these action types
32 our @POSTLOAD = qw/Default/;
36 Catalyst::Dispatcher - The Catalyst Dispatcher
44 This is the class that maps public urls to actions in your Catalyst
45 application based on the attributes you set.
51 Construct a new dispatcher.
57 my $class = ref($self) || $self;
59 my $obj = $class->SUPER::new(@_);
61 # set the default pre- and and postloads
62 $obj->preload_dispatch_types( \@PRELOAD );
63 $obj->postload_dispatch_types( \@POSTLOAD );
64 $obj->action_hash( {} );
65 $obj->container_hash( {} );
67 # Create the root node of the tree
69 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
70 $obj->tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
75 =head2 $self->preload_dispatch_types
77 An arrayref of pre-loaded dispatchtype classes
79 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
80 To use a custom class outside the regular C<Catalyst> namespace, prefix
81 it with a C<+>, like so:
85 =head2 $self->postload_dispatch_types
87 An arrayref of post-loaded dispatchtype classes
89 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
90 To use a custom class outside the regular C<Catalyst> namespace, prefix
91 it with a C<+>, like so:
95 =head2 $self->detach( $c, $command [, \@arguments ] )
97 Documented in L<Catalyst>
102 my ( $self, $c, $command, @args ) = @_;
103 $c->forward( $command, @args ) if $command;
104 die $Catalyst::DETACH;
107 =head2 $self->dispatch($c)
109 Delegate the dispatch to the action that matched the url, or return a
110 message about unknown resource
116 my ( $self, $c ) = @_;
118 $c->forward( join( '/', '', $c->action->namespace, '_DISPATCH' ) );
122 my $path = $c->req->path;
124 ? qq/Unknown resource "$path"/
125 : "No default action defined";
126 $c->log->error($error) if $c->debug;
131 # $self->_command2action( $c, $command [, \@arguments ] )
132 # Search for an action, from the command and returns C<($action, $args)> on
133 # success. Returns C<(0)> on error.
135 sub _command2action {
136 my ( $self, $c, $command, @extra_params ) = @_;
139 $c->log->debug('Nothing to go to') if $c->debug;
145 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
146 @args = @{ pop @extra_params }
148 # this is a copy, it may take some abuse from
149 # ->_invoke_as_path if the path had trailing parts
150 @args = @{ $c->request->arguments };
155 if (Scalar::Util::blessed($command) && $command->isa('Catalyst::Action')) {
159 # go to a string path ("/foo/bar/gorch")
160 # or action object which stringifies to that
161 $action = $self->_invoke_as_path( $c, "$command", \@args );
164 # go to a component ( "MyApp::*::Foo" or $c->component("...")
165 # - a path or an object)
167 my $method = @extra_params ? $extra_params[0] : "process";
168 $action = $self->_invoke_as_component( $c, $command, $method );
171 return $action, \@args;
174 =head2 $self->visit( $c, $command [, \@arguments ] )
176 Documented in L<Catalyst>
182 $self->_do_visit('visit', @_);
188 my ( $c, $command ) = @_;
189 my ( $action, $args ) = $self->_command2action(@_);
190 my $error = qq/Couldn't $opname("$command"): /;
193 $error .= qq/Couldn't $opname to command "$command": /
194 .qq/Invalid action or component./;
196 elsif (!defined $action->namespace) {
197 $error .= qq/Action has no namespace: cannot $opname() to a plain /
198 .qq/method or component, must be a :Action or some sort./
200 elsif (!$action->class->can('_DISPATCH')) {
201 $error .= qq/Action cannot _DISPATCH. /
202 .qq/Did you try to $opname() a non-controller action?/;
210 $c->log->debug($error) if $c->debug;
214 $action = $self->expand_action($action);
216 local $c->request->{arguments} = $args;
217 local $c->{namespace} = $action->{'namespace'};
218 local $c->{action} = $action;
223 =head2 $self->go( $c, $command [, \@arguments ] )
225 Documented in L<Catalyst>
231 $self->_do_visit('go', @_);
235 =head2 $self->forward( $c, $command [, \@arguments ] )
237 Documented in L<Catalyst>
243 my ( $c, $command ) = @_;
244 my ( $action, $args ) = $self->_command2action(@_);
248 qq/Couldn't forward to command "$command": /
249 . qq/Invalid action or component./;
251 $c->log->debug($error) if $c->debug;
255 local $c->request->{arguments} = $args;
256 $action->dispatch( $c );
261 sub _action_rel2abs {
262 my ( $self, $c, $path ) = @_;
264 unless ( $path =~ m#^/# ) {
265 my $namespace = $c->stack->[-1]->namespace;
266 $path = "$namespace/$path";
273 sub _invoke_as_path {
274 my ( $self, $c, $rel_path, $args ) = @_;
276 my $path = $self->_action_rel2abs( $c, $rel_path );
278 my ( $tail, @extra_args );
279 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
280 { # allow $path to be empty
281 if ( my $action = $c->get_action( $tail, $path ) ) {
282 push @$args, @extra_args;
288 ; # if a match on the global namespace failed then the whole lookup failed
291 unshift @extra_args, $tail;
295 sub _find_component_class {
296 my ( $self, $c, $component ) = @_;
298 return ref($component)
299 || ref( $c->component($component) )
300 || $c->component($component);
303 sub _invoke_as_component {
304 my ( $self, $c, $component, $method ) = @_;
306 my $class = $self->_find_component_class( $c, $component ) || return 0;
308 if ( my $code = $class->can($method) ) {
309 return $self->method_action_class->new(
313 reverse => "$class->$method",
315 namespace => Catalyst::Utils::class2prefix(
316 $class, $c->config->{case_sensitive}
323 qq/Couldn't forward to "$class". Does not implement "$method"/;
325 $c->log->debug($error)
331 =head2 $self->prepare_action($c)
333 Find an dispatch type that matches $c->req->path, and set args from it.
338 my ( $self, $c ) = @_;
339 my $path = $c->req->path;
340 my @path = split /\//, $c->req->path;
341 $c->req->args( \my @args );
343 unshift( @path, '' ); # Root action
345 DESCEND: while (@path) {
346 $path = join '/', @path;
349 $path = '' if $path eq '/'; # Root action
351 # Check out dispatch types to see if any will handle the path at
354 foreach my $type ( @{ $self->dispatch_types } ) {
355 last DESCEND if $type->match( $c, $path );
358 # If not, move the last part path to args
359 my $arg = pop(@path);
360 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
364 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$c->req->captures||[]};
366 $c->log->debug( 'Path is "' . $c->req->match . '"' )
367 if ( $c->debug && length $c->req->match );
369 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
370 if ( $c->debug && @args );
373 =head2 $self->get_action( $action, $namespace )
375 returns a named action from a given namespace.
380 my ( $self, $name, $namespace ) = @_;
383 $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
385 return $self->action_hash->{"$namespace/$name"};
388 =head2 $self->get_action_by_path( $path );
390 Returns the named action by its full path.
394 sub get_action_by_path {
395 my ( $self, $path ) = @_;
397 $path = "/$path" unless $path =~ /\//;
398 $self->action_hash->{$path};
401 =head2 $self->get_actions( $c, $action, $namespace )
406 my ( $self, $c, $action, $namespace ) = @_;
407 return [] unless $action;
409 $namespace = join( "/", grep { length } split '/', $namespace || "" );
411 my @match = $self->get_containers($namespace);
413 return map { $_->get_action($action) } @match;
416 =head2 $self->get_containers( $namespace )
418 Return all the action containers for a given namespace, inclusive
423 my ( $self, $namespace ) = @_;
425 $namespace = '' if $namespace eq '/';
429 if ( length $namespace ) {
431 push @containers, $self->container_hash->{$namespace};
432 } while ( $namespace =~ s#/[^/]+$## );
435 return reverse grep { defined } @containers, $self->container_hash->{''};
437 my @parts = split '/', $namespace;
440 =head2 $self->uri_for_action($action, \@captures)
442 Takes a Catalyst::Action object and action parameters and returns a URI
443 part such that if $c->req->path were this URI part, this action would be
444 dispatched to with $c->req->captures set to the supplied arrayref.
446 If the action object is not available for external dispatch or the dispatcher
447 cannot determine an appropriate URI, this method will return undef.
452 my ( $self, $action, $captures) = @_;
454 foreach my $dispatch_type ( @{ $self->dispatch_types } ) {
455 my $uri = $dispatch_type->uri_for_action( $action, $captures );
456 return( $uri eq '' ? '/' : $uri )
464 expand an action into a full representation of the dispatch.
465 mostly useful for chained, other actions will just return a
471 my ($self, $action) = @_;
473 foreach my $dispatch_type (@{ $self->dispatch_types }) {
474 my $expanded = $dispatch_type->expand_action($action);
475 return $expanded if $expanded;
481 =head2 $self->register( $c, $action )
483 Make sure all required dispatch types for this action are loaded, then
484 pass the action to our dispatch types so they can register it if required.
485 Also, set up the tree with the action containers.
490 my ( $self, $c, $action ) = @_;
492 my $registered = $self->registered_dispatch_types;
495 foreach my $key ( keys %{ $action->attributes } ) {
496 next if $key eq 'Private';
497 my $class = "Catalyst::DispatchType::$key";
498 unless ( $registered->{$class} ) {
499 eval "require $class";
500 push( @{ $self->dispatch_types }, $class->new ) unless $@;
501 $registered->{$class} = 1;
505 # Pass the action to our dispatch types so they can register it if reqd.
506 foreach my $type ( @{ $self->dispatch_types } ) {
507 $type->register( $c, $action );
510 my $namespace = $action->namespace;
511 my $name = $action->name;
513 my $container = $self->_find_or_create_action_container($namespace);
515 # Set the method value
516 $container->add_action($action);
518 $self->action_hash->{"$namespace/$name"} = $action;
519 $self->container_hash->{$namespace} = $container;
522 sub _find_or_create_action_container {
523 my ( $self, $namespace ) = @_;
525 my $tree ||= $self->tree;
527 return $tree->getNodeValue unless $namespace;
529 my @namespace = split '/', $namespace;
530 return $self->_find_or_create_namespace_node( $tree, @namespace )
534 sub _find_or_create_namespace_node {
535 my ( $self, $parent, $part, @namespace ) = @_;
537 return $parent unless $part;
540 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
543 my $container = Catalyst::ActionContainer->new($part);
544 $parent->addChild( $child = Tree::Simple->new($container) );
547 $self->_find_or_create_namespace_node( $child, @namespace );
550 =head2 $self->setup_actions( $class, $context )
556 my ( $self, $c ) = @_;
558 $self->dispatch_types( [] );
559 $self->registered_dispatch_types( {} );
560 $self->method_action_class('Catalyst::Action');
561 $self->action_container_class('Catalyst::ActionContainer');
564 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
565 @{ $self->registered_dispatch_types }{@classes} = (1) x @classes;
567 foreach my $comp ( values %{ $c->components } ) {
568 $comp->register_actions($c) if $comp->can('register_actions');
571 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
573 return unless $c->debug;
574 $self->_display_action_tables($c);
577 sub _display_action_tables {
580 my $column_width = Catalyst::Utils::term_width() - 20 - 36 - 12;
581 my $privates = Text::SimpleTable->new(
582 [ 20, 'Private' ], [ 36, 'Class' ], [ $column_width, 'Method' ]
587 my ( $walker, $parent, $prefix ) = @_;
588 $prefix .= $parent->getNodeValue || '';
589 $prefix .= '/' unless $prefix =~ /\/$/;
590 my $node = $parent->getNodeValue->actions;
592 for my $action ( keys %{$node} ) {
593 my $action_obj = $node->{$action};
595 if ( ( $action =~ /^_.*/ )
596 && ( !$c->config->{show_internal_actions} ) );
597 $privates->row( "$prefix$action", $action_obj->class, $action );
601 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
604 $walker->( $walker, $self->tree, '' );
605 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
608 # List all public actions
609 $_->list($c) for @{ $self->dispatch_types };
612 sub _load_dispatch_types {
613 my ( $self, @types ) = @_;
617 # Preload action types
618 for my $type (@types) {
620 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
621 eval "require $class";
622 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
624 push @{ $self->dispatch_types }, $class->new;
626 push @loaded, $class;
632 # Dont document this until someone else is happy with beaviour. Ash 2009/03/16
634 my ($self, $name) = @_;
636 unless ($name =~ s/^\+//) {
637 $name = "Catalyst::DispatchType::" . $name;
640 for (@{ $self->dispatch_types }) {
641 return $_ if ref($_) eq $name;
648 Catalyst Contributors, see Catalyst.pm
652 This program is free software, you can redistribute it and/or modify it under
653 the same terms as Perl itself.