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;
16 use overload '""' => sub { return ref shift }, fallback => 1;
18 __PACKAGE__->mk_accessors(
19 qw/tree dispatch_types registered_dispatch_types
20 method_action_class action_container_class
21 preload_dispatch_types postload_dispatch_types
26 # Preload these action types
27 our @PRELOAD = qw/Index Path Regex/;
29 # Postload these action types
30 our @POSTLOAD = qw/Default/;
34 Catalyst::Dispatcher - The Catalyst Dispatcher
42 This is the class that maps public urls to actions in your Catalyst
43 application based on the attributes you set.
49 Construct a new dispatcher.
55 my $class = ref($self) || $self;
57 my $obj = $class->SUPER::new(@_);
59 # set the default pre- and and postloads
60 $obj->preload_dispatch_types( \@PRELOAD );
61 $obj->postload_dispatch_types( \@POSTLOAD );
62 $obj->action_hash({});
66 =head2 $self->preload_dispatch_types
68 An arrayref of pre-loaded dispatchtype classes
70 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
71 To use a custom class outside the regular C<Catalyst> namespace, prefix
72 it with a C<+>, like so:
76 =head2 $self->postload_dispatch_types
78 An arrayref of post-loaded dispatchtype classes
80 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
81 To use a custom class outside the regular C<Catalyst> namespace, prefix
82 it with a C<+>, like so:
86 =head2 $self->detach( $c, $command [, \@arguments ] )
88 Documented in L<Catalyst>
93 my ( $self, $c, $command, @args ) = @_;
94 $c->forward( $command, @args ) if $command;
95 die $Catalyst::DETACH;
98 =head2 $self->dispatch($c)
100 Delegate the dispatch to the action that matched the url, or return a
101 message about unknown resource
107 my ( $self, $c ) = @_;
109 $c->forward( join( '/', '', $c->action->namespace, '_DISPATCH' ) );
113 my $path = $c->req->path;
115 ? qq/Unknown resource "$path"/
116 : "No default action defined";
117 $c->log->error($error) if $c->debug;
122 =head2 $self->forward( $c, $command [, \@arguments ] )
124 Documented in L<Catalyst>
134 $c->log->debug('Nothing to forward to') if $c->debug;
139 my $arguments = $c->req->args;
140 if ( ref( $_[-1] ) eq 'ARRAY' ) {
141 $arguments = pop(@_);
147 unless ( ref $command ) {
148 my $command_copy = $command;
150 unless ( $command_copy =~ s/^\/// ) {
151 my $namespace = $c->stack->[-1]->namespace;
152 $command_copy = "${namespace}/${command}";
155 unless ( $command_copy =~ /\// ) {
156 $result = $c->get_action( $command_copy, '/' );
160 DESCEND: while ( $command_copy =~ s/^(.*)\/(\w+)$/$1/ ) {
162 $result = $c->get_action( $tail, $1 );
166 unshift( @{$arguments}, @extra_args );
169 unshift( @extra_args, $tail );
176 my $class = ref($command)
177 || ref( $c->component($command) )
178 || $c->component($command);
179 my $method = shift || 'process';
183 qq/Couldn't forward to command "$command". Invalid action or component./;
185 $c->log->debug($error) if $c->debug;
189 if ( my $code = $class->can($method) ) {
190 my $action = $self->method_action_class->new(
194 reverse => "$class->$method",
196 namespace => Catalyst::Utils::class2prefix(
197 $class, $c->config->{case_sensitive}
206 qq/Couldn't forward to "$class". Does not implement "$method"/;
208 $c->log->debug($error)
216 local $c->request->{arguments} = [ @{$arguments} ];
217 $result->execute($c);
219 else { $result->execute($c) }
224 =head2 $self->prepare_action($c)
226 Find an dispatch type that matches $c->req->path, and set args from it.
231 my ( $self, $c ) = @_;
232 my $path = $c->req->path;
233 my @path = split /\//, $c->req->path;
234 $c->req->args( \my @args );
236 unshift( @path, '' ); # Root action
238 DESCEND: while (@path) {
239 $path = join '/', @path;
242 $path = '' if $path eq '/'; # Root action
244 # Check out dispatch types to see if any will handle the path at
247 foreach my $type ( @{ $self->dispatch_types } ) {
248 last DESCEND if $type->match( $c, $path );
251 # If not, move the last part path to args
252 my $arg = pop(@path);
253 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
257 $c->log->debug( 'Path is "' . $c->req->match . '"' )
258 if ( $c->debug && $c->req->match );
260 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
261 if ( $c->debug && @args );
264 =head2 $self->get_action( $action, $namespace )
266 returns a named action from a given namespace.
271 my ( $self, $name, $namespace ) = @_;
274 $namespace = '' if $namespace eq '/';
276 return $self->action_hash->{ "$namespace/$name" };
279 =head2 $self->get_actions( $c, $action, $namespace )
284 my ( $self, $c, $action, $namespace ) = @_;
285 return [] unless $action;
287 $namespace = '' if $namespace eq '/';
289 my @match = $self->get_containers($namespace);
291 return map { $_->get_action($action) } @match;
294 =head2 $self->get_containers( $namespace )
296 Return all the action containers for a given namespace, inclusive
301 my ( $self, $namespace ) = @_;
303 # If the namespace is / just return the root ActionContainer
305 return ( $self->tree->getNodeValue )
306 if ( !$namespace || ( $namespace eq '/' ) );
308 # Use a visitor to recurse down the tree finding the ActionContainers
309 # for each namespace in the chain.
311 my $visitor = Tree::Simple::Visitor::FindByPath->new;
312 my @path = split( '/', $namespace );
313 $visitor->setSearchPath(@path);
314 $self->tree->accept($visitor);
316 my @match = $visitor->getResults;
317 @match = ( $self->tree ) unless @match;
319 if ( !defined $visitor->getResult ) {
321 # If we don't manage to match, the visitor doesn't return the last
322 # node is matched, so foo/bar/baz would only find the 'foo' node,
323 # not the foo and foo/bar nodes as it should. This does another
324 # single-level search to see if that's the case, and the 'last unless'
325 # should catch any failures - or short-circuit this if this *is* a
326 # bug in the visitor and gets fixed.
328 if ( my $extra = $path[ ( scalar @match ) - 1 ] ) {
329 $visitor->setSearchPath($extra);
330 $match[-1]->accept($visitor);
331 push( @match, $visitor->getResult ) if defined $visitor->getResult;
335 return map { $_->getNodeValue } @match;
338 =head2 $self->register( $c, $action )
340 Make sure all required dispatch types for this action are loaded, then
341 pass the action to our dispatch types so they can register it if required.
342 Also, set up the tree with the action containers.
347 my ( $self, $c, $action ) = @_;
349 my $registered = $self->registered_dispatch_types;
352 foreach my $key ( keys %{ $action->attributes } ) {
353 $priv++ if $key eq 'Private';
354 my $class = "Catalyst::DispatchType::$key";
355 unless ( $registered->{$class} ) {
356 eval "require $class";
357 push( @{ $self->dispatch_types }, $class->new ) unless $@;
358 $registered->{$class} = 1;
362 # Pass the action to our dispatch types so they can register it if reqd.
364 foreach my $type ( @{ $self->dispatch_types } ) {
365 $reg++ if $type->register( $c, $action );
368 return unless $reg + $priv;
370 my $namespace = $action->namespace;
371 my $name = $action->name;
373 my $node = $self->find_or_create_namespace_node( $namespace );
375 # Set the method value
376 $node->getNodeValue->actions->{ $name } = $action;
378 my $path = "$namespace/$name";
380 if ( exists $self->action_hash->{$path} and $self->action_hash->{$path} != $action ) {
381 warn "inconsistency: $path is already registered";
384 $self->action_hash->{$path} = $action;
387 sub find_or_create_namespace_node {
388 my ( $self, $namespace ) = @_;
390 my $tree ||= $self->tree;
392 return $tree unless $namespace;
394 my @namespace = split '/', $namespace;
395 return $self->_find_or_create_namespace_node( $tree, @namespace );
398 sub _find_or_create_namespace_node {
399 my ( $self, $parent, $part, @namespace ) = @_;
401 return $parent unless $part;
403 my $child = ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
406 # Create a new tree node and an ActionContainer to form
410 Catalyst::ActionContainer->new(
411 { part => $part, actions => {} } );
413 $parent->addChild( $child = Tree::Simple->new($container) );
416 $self->_find_or_create_namespace_node( $child, @namespace );
419 =head2 $self->setup_actions( $class, $context )
425 my ( $self, $c ) = @_;
427 $self->dispatch_types( [] );
428 $self->registered_dispatch_types( {} );
429 $self->method_action_class('Catalyst::Action');
430 $self->action_container_class('Catalyst::ActionContainer');
433 $self->do_load_dispatch_types( @{ $self->preload_dispatch_types } );
434 @{ $self->registered_dispatch_types }{@classes} = (1) x @classes;
436 # Create the root node of the tree
438 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
439 $self->tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
441 foreach my $comp ( values %{ $c->components } ) {
442 $comp->register_actions($c) if $comp->can('register_actions');
445 $self->do_load_dispatch_types( @{ $self->postload_dispatch_types } );
447 return unless $c->debug;
449 my $privates = Text::SimpleTable->new(
457 my ( $walker, $parent, $prefix ) = @_;
458 $prefix .= $parent->getNodeValue || '';
459 $prefix .= '/' unless $prefix =~ /\/$/;
460 my $node = $parent->getNodeValue->actions;
462 for my $action ( keys %{$node} ) {
463 my $action_obj = $node->{$action};
465 if ( ( $action =~ /^_.*/ )
466 && ( !$c->config->{show_internal_actions} ) );
467 $privates->row( "$prefix$action", $action_obj->class, $action );
471 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
474 $walker->( $walker, $self->tree, '' );
475 $c->log->debug( "Loaded Private actions:\n" . $privates->draw )
478 # List all public actions
479 $_->list($c) for @{ $self->dispatch_types };
482 sub do_load_dispatch_types {
483 my ( $self, @types ) = @_;
487 # Preload action types
488 for my $type (@types) {
490 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
491 eval "require $class";
492 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
494 push @{ $self->dispatch_types }, $class->new;
496 push @loaded, $class;
504 Sebastian Riedel, C<sri@cpan.org>
505 Matt S Trout, C<mst@shadowcatsystems.co.uk>
509 This program is free software, you can redistribute it and/or modify it under
510 the same terms as Perl itself.