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;
11 use Text::SimpleTable;
13 use Tree::Simple::Visitor::FindByPath;
17 use overload '""' => sub { return ref shift }, fallback => 1;
19 __PACKAGE__->mk_accessors(
20 qw/tree dispatch_types registered_dispatch_types
21 method_action_class action_container_class
22 preload_dispatch_types postload_dispatch_types
23 action_hash container_hash
27 # Preload these action types
28 our @PRELOAD = qw/Index Path Regex/;
30 # Postload these action types
31 our @POSTLOAD = qw/Default/;
35 Catalyst::Dispatcher - The Catalyst Dispatcher
43 This is the class that maps public urls to actions in your Catalyst
44 application based on the attributes you set.
50 Construct a new dispatcher.
56 my $class = ref($self) || $self;
58 my $obj = $class->SUPER::new(@_);
60 # set the default pre- and and postloads
61 $obj->preload_dispatch_types( \@PRELOAD );
62 $obj->postload_dispatch_types( \@POSTLOAD );
63 $obj->action_hash( {} );
64 $obj->container_hash( {} );
66 # Create the root node of the tree
68 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
69 $obj->tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
74 =head2 $self->preload_dispatch_types
76 An arrayref of pre-loaded dispatchtype classes
78 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
79 To use a custom class outside the regular C<Catalyst> namespace, prefix
80 it with a C<+>, like so:
84 =head2 $self->postload_dispatch_types
86 An arrayref of post-loaded dispatchtype classes
88 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
89 To use a custom class outside the regular C<Catalyst> namespace, prefix
90 it with a C<+>, like so:
94 =head2 $self->detach( $c, $command [, \@arguments ] )
96 Documented in L<Catalyst>
101 my ( $self, $c, $command, @args ) = @_;
102 $c->forward( $command, @args ) if $command;
103 die $Catalyst::DETACH;
106 =head2 $self->dispatch($c)
108 Delegate the dispatch to the action that matched the url, or return a
109 message about unknown resource
115 my ( $self, $c ) = @_;
117 $c->forward( join( '/', '', $c->action->namespace, '_DISPATCH' ) );
121 my $path = $c->req->path;
123 ? qq/Unknown resource "$path"/
124 : "No default action defined";
125 $c->log->error($error) if $c->debug;
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.
134 sub _command2action {
135 my ( $self, $c, $command, @extra_params ) = @_;
138 $c->log->debug('Nothing to go to') if $c->debug;
144 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
145 @args = @{ pop @extra_params }
147 # this is a copy, it may take some abuse from
148 # ->_invoke_as_path if the path had trailing parts
149 @args = @{ $c->request->arguments };
154 if (Scalar::Util::blessed($command) && $command->isa('Catalyst::Action')) {
158 # go to a string path ("/foo/bar/gorch")
159 # or action object which stringifies to that
160 $action = $self->_invoke_as_path( $c, "$command", \@args );
163 # go to a component ( "MyApp::*::Foo" or $c->component("...")
164 # - a path or an object)
166 my $method = @extra_params ? $extra_params[0] : "process";
167 $action = $self->_invoke_as_component( $c, $command, $method );
170 return $action, \@args;
173 =head2 $self->visit( $c, $command [, \@arguments ] )
175 Documented in L<Catalyst>
181 $self->_do_visit('visit', @_);
187 my ( $c, $command ) = @_;
188 my ( $action, $args ) = $self->_command2action(@_);
189 my $error = qq/Couldn't $opname("$command"): /;
192 $error .= qq/Couldn't $opname to command "$command": /
193 .qq/Invalid action or component./;
195 elsif (!defined $action->namespace) {
196 $error .= qq/Action has no namespace: cannot $opname() to a plain /
197 .qq/method or component, must be a :Action or some sort./
199 elsif (!$action->class->can('_DISPATCH')) {
200 $error .= qq/Action cannot _DISPATCH. /
201 .qq/Did you try to $opname() a non-controller action?/;
209 $c->log->debug($error) if $c->debug;
213 $action = $self->expand_action($action);
215 local $c->request->{arguments} = $args;
216 local $c->{namespace} = $action->{'namespace'};
217 local $c->{action} = $action;
222 =head2 $self->go( $c, $command [, \@arguments ] )
224 Documented in L<Catalyst>
230 $self->_do_visit('go', @_);
234 =head2 $self->forward( $c, $command [, \@arguments ] )
236 Documented in L<Catalyst>
242 my ( $c, $command ) = @_;
243 my ( $action, $args ) = $self->_command2action(@_);
247 qq/Couldn't forward to command "$command": /
248 . qq/Invalid action or component./;
250 $c->log->debug($error) if $c->debug;
254 local $c->request->{arguments} = $args;
255 $action->dispatch( $c );
260 sub _action_rel2abs {
261 my ( $self, $c, $path ) = @_;
263 unless ( $path =~ m#^/# ) {
264 my $namespace = $c->stack->[-1]->namespace;
265 $path = "$namespace/$path";
272 sub _invoke_as_path {
273 my ( $self, $c, $rel_path, $args ) = @_;
275 my $path = $self->_action_rel2abs( $c, $rel_path );
277 my ( $tail, @extra_args );
278 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
279 { # allow $path to be empty
280 if ( my $action = $c->get_action( $tail, $path ) ) {
281 push @$args, @extra_args;
287 ; # if a match on the global namespace failed then the whole lookup failed
290 unshift @extra_args, $tail;
294 sub _find_component_class {
295 my ( $self, $c, $component ) = @_;
297 return ref($component)
298 || ref( $c->component($component) )
299 || $c->component($component);
302 sub _invoke_as_component {
303 my ( $self, $c, $component, $method ) = @_;
305 my $class = $self->_find_component_class( $c, $component ) || return 0;
307 if ( my $code = $class->can($method) ) {
308 return $self->method_action_class->new(
312 reverse => "$class->$method",
314 namespace => Catalyst::Utils::class2prefix(
315 $class, $c->config->{case_sensitive}
322 qq/Couldn't forward to "$class". Does not implement "$method"/;
324 $c->log->debug($error)
330 =head2 $self->prepare_action($c)
332 Find an dispatch type that matches $c->req->path, and set args from it.
337 my ( $self, $c ) = @_;
338 my $path = $c->req->path;
339 my @path = split /\//, $c->req->path;
340 $c->req->args( \my @args );
342 unshift( @path, '' ); # Root action
344 DESCEND: while (@path) {
345 $path = join '/', @path;
348 $path = '' if $path eq '/'; # Root action
350 # Check out dispatch types to see if any will handle the path at
353 foreach my $type ( @{ $self->dispatch_types } ) {
354 last DESCEND if $type->match( $c, $path );
357 # If not, move the last part path to args
358 my $arg = pop(@path);
359 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
363 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$c->req->captures||[]};
365 $c->log->debug( 'Path is "' . $c->req->match . '"' )
366 if ( $c->debug && length $c->req->match );
368 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
369 if ( $c->debug && @args );
372 =head2 $self->get_action( $action, $namespace )
374 returns a named action from a given namespace.
379 my ( $self, $name, $namespace ) = @_;
382 $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
384 return $self->action_hash->{"$namespace/$name"};
387 =head2 $self->get_action_by_path( $path );
389 Returns the named action by its full path.
393 sub get_action_by_path {
394 my ( $self, $path ) = @_;
396 $path = "/$path" unless $path =~ /\//;
397 $self->action_hash->{$path};
400 =head2 $self->get_actions( $c, $action, $namespace )
405 my ( $self, $c, $action, $namespace ) = @_;
406 return [] unless $action;
408 $namespace = join( "/", grep { length } split '/', $namespace || "" );
410 my @match = $self->get_containers($namespace);
412 return map { $_->get_action($action) } @match;
415 =head2 $self->get_containers( $namespace )
417 Return all the action containers for a given namespace, inclusive
422 my ( $self, $namespace ) = @_;
424 $namespace = '' if $namespace eq '/';
428 if ( length $namespace ) {
430 push @containers, $self->container_hash->{$namespace};
431 } while ( $namespace =~ s#/[^/]+$## );
434 return reverse grep { defined } @containers, $self->container_hash->{''};
436 my @parts = split '/', $namespace;
439 =head2 $self->uri_for_action($action, \@captures)
441 Takes a Catalyst::Action object and action parameters and returns a URI
442 part such that if $c->req->path were this URI part, this action would be
443 dispatched to with $c->req->captures set to the supplied arrayref.
445 If the action object is not available for external dispatch or the dispatcher
446 cannot determine an appropriate URI, this method will return undef.
451 my ( $self, $action, $captures) = @_;
453 foreach my $dispatch_type ( @{ $self->dispatch_types } ) {
454 my $uri = $dispatch_type->uri_for_action( $action, $captures );
455 return( $uri eq '' ? '/' : $uri )
463 expand an action into a full representation of the dispatch.
464 mostly useful for chained, other actions will just return a
470 my ($self, $action) = @_;
472 foreach my $dispatch_type (@{ $self->dispatch_types }) {
473 my $expanded = $dispatch_type->expand_action($action);
474 return $expanded if $expanded;
480 =head2 $self->register( $c, $action )
482 Make sure all required dispatch types for this action are loaded, then
483 pass the action to our dispatch types so they can register it if required.
484 Also, set up the tree with the action containers.
489 my ( $self, $c, $action ) = @_;
491 my $registered = $self->registered_dispatch_types;
494 foreach my $key ( keys %{ $action->attributes } ) {
495 next if $key eq 'Private';
496 my $class = "Catalyst::DispatchType::$key";
497 unless ( $registered->{$class} ) {
498 eval "require $class";
499 push( @{ $self->dispatch_types }, $class->new ) unless $@;
500 $registered->{$class} = 1;
504 # Pass the action to our dispatch types so they can register it if reqd.
505 foreach my $type ( @{ $self->dispatch_types } ) {
506 $type->register( $c, $action );
509 my $namespace = $action->namespace;
510 my $name = $action->name;
512 my $container = $self->_find_or_create_action_container($namespace);
514 # Set the method value
515 $container->add_action($action);
517 $self->action_hash->{"$namespace/$name"} = $action;
518 $self->container_hash->{$namespace} = $container;
521 sub _find_or_create_action_container {
522 my ( $self, $namespace ) = @_;
524 my $tree ||= $self->tree;
526 return $tree->getNodeValue unless $namespace;
528 my @namespace = split '/', $namespace;
529 return $self->_find_or_create_namespace_node( $tree, @namespace )
533 sub _find_or_create_namespace_node {
534 my ( $self, $parent, $part, @namespace ) = @_;
536 return $parent unless $part;
539 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
542 my $container = Catalyst::ActionContainer->new($part);
543 $parent->addChild( $child = Tree::Simple->new($container) );
546 $self->_find_or_create_namespace_node( $child, @namespace );
549 =head2 $self->setup_actions( $class, $context )
555 my ( $self, $c ) = @_;
557 $self->dispatch_types( [] );
558 $self->registered_dispatch_types( {} );
559 $self->method_action_class('Catalyst::Action');
560 $self->action_container_class('Catalyst::ActionContainer');
563 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
564 @{ $self->registered_dispatch_types }{@classes} = (1) x @classes;
566 foreach my $comp ( values %{ $c->components } ) {
567 $comp->register_actions($c) if $comp->can('register_actions');
570 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
572 return unless $c->debug;
574 my $privates = Text::SimpleTable->new(
582 my ( $walker, $parent, $prefix ) = @_;
583 $prefix .= $parent->getNodeValue || '';
584 $prefix .= '/' unless $prefix =~ /\/$/;
585 my $node = $parent->getNodeValue->actions;
587 for my $action ( keys %{$node} ) {
588 my $action_obj = $node->{$action};
590 if ( ( $action =~ /^_.*/ )
591 && ( !$c->config->{show_internal_actions} ) );
592 $privates->row( "$prefix$action", $action_obj->class, $action );
596 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
599 $walker->( $walker, $self->tree, '' );
600 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
603 # List all public actions
604 $_->list($c) for @{ $self->dispatch_types };
607 sub _load_dispatch_types {
608 my ( $self, @types ) = @_;
612 # Preload action types
613 for my $type (@types) {
615 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
616 eval "require $class";
617 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
619 push @{ $self->dispatch_types }, $class->new;
621 push @loaded, $class;
629 Catalyst Contributors, see Catalyst.pm
633 This program is free software, you can redistribute it and/or modify it under
634 the same terms as Perl itself.