1 package Catalyst::Dispatcher;
6 use Catalyst::Exception;
9 use Catalyst::ActionContainer;
10 use Catalyst::DispatchType::Default;
11 use Catalyst::DispatchType::Index;
12 use Text::SimpleTable;
14 use Tree::Simple::Visitor::FindByPath;
17 #do these belong as package vars or should we build these via a builder method?
18 # Preload these action types
19 our @PRELOAD = qw/Index Path Regex/;
21 # Postload these action types
22 our @POSTLOAD = qw/Default/;
24 has _tree => (is => 'rw');
25 has _dispatch_types => (is => 'rw', default => sub { [] }, required => 1, lazy => 1);
26 has _registered_dispatch_types => (is => 'rw', default => sub { {} }, required => 1, lazy => 1);
27 has _method_action_class => (is => 'rw', default => 'Catalyst::Action');
28 has _action_container_class => (is => 'rw', default => 'Catalyst::ActionContainer');
30 has preload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@PRELOAD] });
31 has postload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@POSTLOAD] });
32 has _action_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
33 has _container_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
39 Catalyst::Dispatcher - The Catalyst Dispatcher
47 This is the class that maps public urls to actions in your Catalyst
48 application based on the attributes you set.
54 Construct a new dispatcher.
59 my ($self, $params) = @_;
62 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
64 $self->_tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
67 =head2 $self->preload_dispatch_types
69 An arrayref of pre-loaded dispatchtype classes
71 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
72 To use a custom class outside the regular C<Catalyst> namespace, prefix
73 it with a C<+>, like so:
77 =head2 $self->postload_dispatch_types
79 An arrayref of post-loaded dispatchtype classes
81 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
82 To use a custom class outside the regular C<Catalyst> namespace, prefix
83 it with a C<+>, like so:
87 =head2 $self->dispatch($c)
89 Delegate the dispatch to the action that matched the url, or return a
90 message about unknown resource
96 my ( $self, $c ) = @_;
97 if ( my $action = $c->action ) {
98 $c->forward( join( '/', '', $action->namespace, '_DISPATCH' ) );
102 my $path = $c->req->path;
104 ? qq/Unknown resource "$path"/
105 : "No default action defined";
106 $c->log->error($error) if $c->debug;
111 # $self->_command2action( $c, $command [, \@arguments ] )
112 # Search for an action, from the command and returns C<($action, $args)> on
113 # success. Returns C<(0)> on error.
115 sub _command2action {
116 my ( $self, $c, $command, @extra_params ) = @_;
119 $c->log->debug('Nothing to go to') if $c->debug;
125 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
126 @args = @{ pop @extra_params }
128 # this is a copy, it may take some abuse from
129 # ->_invoke_as_path if the path had trailing parts
130 @args = @{ $c->request->arguments };
135 # go to a string path ("/foo/bar/gorch")
136 # or action object which stringifies to that
137 $action = $self->_invoke_as_path( $c, "$command", \@args );
139 # go to a component ( "MyApp::*::Foo" or $c->component("...")
140 # - a path or an object)
142 my $method = @extra_params ? $extra_params[0] : "process";
143 $action = $self->_invoke_as_component( $c, $command, $method );
146 return $action, \@args;
149 =head2 $self->visit( $c, $command [, \@arguments ] )
151 Documented in L<Catalyst>
157 $self->_do_visit('visit', @_);
163 my ( $c, $command ) = @_;
164 my ( $action, $args ) = $self->_command2action(@_);
165 my $error = qq/Couldn't $opname("$command"): /;
168 $error .= qq/Couldn't $opname to command "$command": /
169 .qq/Invalid action or component./;
171 elsif (!defined $action->namespace) {
172 $error .= qq/Action has no namespace: cannot $opname() to a plain /
173 .qq/method or component, must be a :Action or some sort./
175 elsif (!$action->class->can('_DISPATCH')) {
176 $error .= qq/Action cannot _DISPATCH. /
177 .qq/Did you try to $opname() a non-controller action?/;
185 $c->log->debug($error) if $c->debug;
189 $action = $self->expand_action($action);
191 local $c->request->{arguments} = $args;
192 local $c->{namespace} = $action->{'namespace'};
193 local $c->{action} = $action;
198 =head2 $self->go( $c, $command [, \@arguments ] )
200 Documented in L<Catalyst>
206 $self->_do_visit('go', @_);
210 =head2 $self->forward( $c, $command [, \@arguments ] )
212 Documented in L<Catalyst>
218 $self->_do_forward(forward => @_);
224 my ( $c, $command ) = @_;
225 my ( $action, $args ) = $self->_command2action(@_);
228 my $error .= qq/Couldn't $opname to command "$command": /
229 .qq/Invalid action or component./;
231 $c->log->debug($error) if $c->debug;
235 no warnings 'recursion';
237 my $orig_args = $c->request->arguments();
238 $c->request->arguments($args);
239 $action->dispatch( $c );
240 $c->request->arguments($orig_args);
245 =head2 $self->detach( $c, $command [, \@arguments ] )
247 Documented in L<Catalyst>
252 my ( $self, $c, $command, @args ) = @_;
253 $self->_do_forward(detach => $c, $command, @args ) if $command;
254 die $Catalyst::DETACH;
257 sub _action_rel2abs {
258 my ( $self, $c, $path ) = @_;
260 unless ( $path =~ m#^/# ) {
261 my $namespace = $c->stack->[-1]->namespace;
262 $path = "$namespace/$path";
269 sub _invoke_as_path {
270 my ( $self, $c, $rel_path, $args ) = @_;
272 my $path = $self->_action_rel2abs( $c, $rel_path );
274 my ( $tail, @extra_args );
275 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
276 { # allow $path to be empty
277 if ( my $action = $c->get_action( $tail, $path ) ) {
278 push @$args, @extra_args;
284 ; # if a match on the global namespace failed then the whole lookup failed
287 unshift @extra_args, $tail;
291 sub _find_component_class {
292 my ( $self, $c, $component ) = @_;
294 return ref($component)
295 || ref( $c->component($component) )
296 || $c->component($component);
299 sub _invoke_as_component {
300 my ( $self, $c, $component, $method ) = @_;
302 my $class = $self->_find_component_class( $c, $component ) || return 0;
304 if ( my $code = $class->can($method) ) {
305 return $self->_method_action_class->new(
309 reverse => "$class->$method",
311 namespace => Catalyst::Utils::class2prefix(
312 $class, $c->config->{case_sensitive}
319 qq/Couldn't forward to "$class". Does not implement "$method"/;
321 $c->log->debug($error)
327 =head2 $self->prepare_action($c)
329 Find an dispatch type that matches $c->req->path, and set args from it.
334 my ( $self, $c ) = @_;
336 my $path = $req->path;
337 my @path = split /\//, $req->path;
338 $req->args( \my @args );
340 unshift( @path, '' ); # Root action
342 DESCEND: while (@path) {
343 $path = join '/', @path;
346 $path = '' if $path eq '/'; # Root action
348 # Check out dispatch types to see if any will handle the path at
351 foreach my $type ( @{ $self->_dispatch_types } ) {
352 last DESCEND if $type->match( $c, $path );
355 # If not, move the last part path to args
356 my $arg = pop(@path);
357 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
361 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$req->captures||[]};
363 $c->log->debug( 'Path is "' . $req->match . '"' )
364 if ( $c->debug && defined $req->match && length $req->match );
366 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
367 if ( $c->debug && @args );
370 =head2 $self->get_action( $action, $namespace )
372 returns a named action from a given namespace.
377 my ( $self, $name, $namespace ) = @_;
380 $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
382 return $self->_action_hash->{"${namespace}/${name}"};
385 =head2 $self->get_action_by_path( $path );
387 Returns the named action by its full path.
391 sub get_action_by_path {
392 my ( $self, $path ) = @_;
394 $path = "/$path" unless $path =~ /\//;
395 $self->_action_hash->{$path};
398 =head2 $self->get_actions( $c, $action, $namespace )
403 my ( $self, $c, $action, $namespace ) = @_;
404 return [] unless $action;
406 $namespace = join( "/", grep { length } split '/', $namespace || "" );
408 my @match = $self->get_containers($namespace);
410 return map { $_->get_action($action) } @match;
413 =head2 $self->get_containers( $namespace )
415 Return all the action containers for a given namespace, inclusive
420 my ( $self, $namespace ) = @_;
422 $namespace = '' if $namespace eq '/';
426 if ( length $namespace ) {
428 push @containers, $self->_container_hash->{$namespace};
429 } while ( $namespace =~ s#/[^/]+$## );
432 return reverse grep { defined } @containers, $self->_container_hash->{''};
434 #return (split '/', $namespace); # isnt this more clear?
435 my @parts = split '/', $namespace;
438 =head2 $self->uri_for_action($action, \@captures)
440 Takes a Catalyst::Action object and action parameters and returns a URI
441 part such that if $c->req->path were this URI part, this action would be
442 dispatched to with $c->req->captures set to the supplied arrayref.
444 If the action object is not available for external dispatch or the dispatcher
445 cannot determine an appropriate URI, this method will return undef.
450 my ( $self, $action, $captures) = @_;
452 foreach my $dispatch_type ( @{ $self->_dispatch_types } ) {
453 my $uri = $dispatch_type->uri_for_action( $action, $captures );
454 return( $uri eq '' ? '/' : $uri )
462 expand an action into a full representation of the dispatch.
463 mostly useful for chained, other actions will just return a
469 my ($self, $action) = @_;
471 foreach my $dispatch_type (@{ $self->_dispatch_types }) {
472 my $expanded = $dispatch_type->expand_action($action);
473 return $expanded if $expanded;
479 =head2 $self->register( $c, $action )
481 Make sure all required dispatch types for this action are loaded, then
482 pass the action to our dispatch types so they can register it if required.
483 Also, set up the tree with the action containers.
488 my ( $self, $c, $action ) = @_;
490 my $registered = $self->_registered_dispatch_types;
492 #my $priv = 0; #seems to be unused
493 foreach my $key ( keys %{ $action->attributes } ) {
494 next if $key eq 'Private';
495 my $class = "Catalyst::DispatchType::$key";
496 unless ( $registered->{$class} ) {
497 #some error checking rethrowing here wouldn't hurt.
498 eval { Class::MOP::load_class($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 ) = @_;
559 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
560 @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
562 foreach my $comp ( values %{ $c->components } ) {
563 $comp->register_actions($c) if $comp->can('register_actions');
566 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
568 return unless $c->debug;
570 my $privates = Text::SimpleTable->new(
578 my ( $walker, $parent, $prefix ) = @_;
579 $prefix .= $parent->getNodeValue || '';
580 $prefix .= '/' unless $prefix =~ /\/$/;
581 my $node = $parent->getNodeValue->actions;
583 for my $action ( keys %{$node} ) {
584 my $action_obj = $node->{$action};
586 if ( ( $action =~ /^_.*/ )
587 && ( !$c->config->{show_internal_actions} ) );
588 $privates->row( "$prefix$action", $action_obj->class, $action );
592 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
595 $walker->( $walker, $self->_tree, '' );
596 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
599 # List all public actions
600 $_->list($c) for @{ $self->_dispatch_types };
603 sub _load_dispatch_types {
604 my ( $self, @types ) = @_;
608 # Preload action types
609 for my $type (@types) {
611 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
613 eval { Class::MOP::load_class($class) };
614 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
616 push @{ $self->_dispatch_types }, $class->new;
618 push @loaded, $class;
625 __PACKAGE__->meta->make_immutable;
633 Catalyst Contributors, see Catalyst.pm
637 This program is free software, you can redistribute it and/or modify it under
638 the same terms as Perl itself.