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
22 action_hash container_hash
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( {} );
63 $obj->container_hash( {} );
65 # Create the root node of the tree
67 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
68 $obj->tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
73 =head2 $self->preload_dispatch_types
75 An arrayref of pre-loaded dispatchtype classes
77 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
78 To use a custom class outside the regular C<Catalyst> namespace, prefix
79 it with a C<+>, like so:
83 =head2 $self->postload_dispatch_types
85 An arrayref of post-loaded dispatchtype classes
87 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
88 To use a custom class outside the regular C<Catalyst> namespace, prefix
89 it with a C<+>, like so:
93 =head2 $self->detach( $c, $command [, \@arguments ] )
95 Documented in L<Catalyst>
100 my ( $self, $c, $command, @args ) = @_;
101 $c->forward( $command, @args ) if $command;
102 die $Catalyst::DETACH;
105 =head2 $self->dispatch($c)
107 Delegate the dispatch to the action that matched the url, or return a
108 message about unknown resource
114 my ( $self, $c ) = @_;
116 $c->forward( join( '/', '', $c->action->namespace, '_DISPATCH' ) );
120 my $path = $c->req->path;
122 ? qq/Unknown resource "$path"/
123 : "No default action defined";
124 $c->log->error($error) if $c->debug;
129 =head2 $self->forward( $c, $command [, \@arguments ] )
131 Documented in L<Catalyst>
137 my ( $self, $c, $command ) = splice( @_, 0, 3 );
140 $c->log->debug('Nothing to forward to') if $c->debug;
144 my $args = [ @{ $c->request->arguments } ];
146 @$args = @{ pop @_ } if ( ref( $_[-1] ) eq 'ARRAY' );
148 my $action = $self->_invoke_as_path( $c, $command, $args )
149 || $self->_invoke_as_component( $c, $command, shift );
152 my $error = qq/Couldn't forward to command "$command": / . qq/Invalid action or component./;
154 $c->log->debug($error) if $c->debug;
160 local $c->request->{arguments} = $args;
161 $action->execute($c);
166 sub _action_rel2abs {
167 my ( $self, $c, $path ) = @_;
169 unless ( $path =~ m#^/# ) {
170 my $namespace = $c->stack->[-1]->namespace;
171 $path = "$namespace/$path";
178 sub _invoke_as_path {
179 my ( $self, $c, $rel_path, $args ) = @_;
181 return if ref $rel_path; # it must be a string
183 my $path = $self->_action_rel2abs( $c, $rel_path );
185 my ($tail, @extra_args);
186 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) ) { # allow $path to be empty
187 if ( my $action = $c->get_action( $tail, $path ) ) {
188 push @$args, @extra_args;
191 return unless $path; # if a match on the global namespace failed then the whole lookup failed
194 unshift @extra_args, $tail;
198 sub _find_component_class {
199 my ( $self, $c, $component ) = @_;
201 return ref($component)
202 || ref( $c->component($component) )
203 || $c->component($component)
206 sub _invoke_as_component {
207 my ( $self, $c, $component, $method ) = @_;
209 my $class = $self->_find_component_class( $c, $component ) || return 0;
210 $method ||= "process";
212 if ( my $code = $class->can($method) ) {
213 return $self->method_action_class->new(
217 reverse => "$class->$method",
219 namespace => Catalyst::Utils::class2prefix(
220 $class, $c->config->{case_sensitive}
226 qq/Couldn't forward to "$class". Does not implement "$method"/;
228 $c->log->debug($error)
234 =head2 $self->prepare_action($c)
236 Find an dispatch type that matches $c->req->path, and set args from it.
241 my ( $self, $c ) = @_;
242 my $path = $c->req->path;
243 my @path = split /\//, $c->req->path;
244 $c->req->args( \my @args );
246 unshift( @path, '' ); # Root action
248 DESCEND: while (@path) {
249 $path = join '/', @path;
252 $path = '' if $path eq '/'; # Root action
254 # Check out dispatch types to see if any will handle the path at
257 foreach my $type ( @{ $self->dispatch_types } ) {
258 last DESCEND if $type->match( $c, $path );
261 # If not, move the last part path to args
262 my $arg = pop(@path);
263 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
267 $c->log->debug( 'Path is "' . $c->req->match . '"' )
268 if ( $c->debug && $c->req->match );
270 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
271 if ( $c->debug && @args );
274 =head2 $self->get_action( $action, $namespace )
276 returns a named action from a given namespace.
281 my ( $self, $name, $namespace ) = @_;
284 $namespace = '' if $namespace eq '/';
286 return $self->action_hash->{"$namespace/$name"};
289 =head2 $self->get_actions( $c, $action, $namespace )
294 my ( $self, $c, $action, $namespace ) = @_;
295 return [] unless $action;
297 $namespace = '' if $namespace eq '/';
299 my @match = $self->get_containers($namespace);
301 return map { $_->get_action($action) } @match;
304 =head2 $self->get_containers( $namespace )
306 Return all the action containers for a given namespace, inclusive
311 my ( $self, $namespace ) = @_;
313 $namespace = '' if $namespace eq '/';
318 push @containers, $self->container_hash->{$namespace};
319 } while ( $namespace =~ s#/[^/]+$## );
321 return reverse grep { defined } @containers, $self->container_hash->{''};
323 my @parts = split '/', $namespace;
326 =head2 $self->register( $c, $action )
328 Make sure all required dispatch types for this action are loaded, then
329 pass the action to our dispatch types so they can register it if required.
330 Also, set up the tree with the action containers.
335 my ( $self, $c, $action ) = @_;
337 my $registered = $self->registered_dispatch_types;
340 foreach my $key ( keys %{ $action->attributes } ) {
341 $priv++ if $key eq 'Private';
342 my $class = "Catalyst::DispatchType::$key";
343 unless ( $registered->{$class} ) {
344 eval "require $class";
345 push( @{ $self->dispatch_types }, $class->new ) unless $@;
346 $registered->{$class} = 1;
350 # Pass the action to our dispatch types so they can register it if reqd.
352 foreach my $type ( @{ $self->dispatch_types } ) {
353 $reg++ if $type->register( $c, $action );
356 return unless $reg + $priv;
358 my $namespace = $action->namespace;
359 my $name = $action->name;
361 my $container = $self->find_or_create_action_container($namespace);
363 # Set the method value
364 $container->add_action($action);
366 $self->action_hash->{"$namespace/$name"} = $action;
367 $self->container_hash->{$namespace} = $container;
370 sub find_or_create_action_container {
371 my ( $self, $namespace ) = @_;
373 my $tree ||= $self->tree;
375 return $tree->getNodeValue unless $namespace;
377 my @namespace = split '/', $namespace;
378 return $self->_find_or_create_namespace_node( $tree, @namespace )
382 sub _find_or_create_namespace_node {
383 my ( $self, $parent, $part, @namespace ) = @_;
385 return $parent unless $part;
388 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
391 my $container = Catalyst::ActionContainer->new($part);
392 $parent->addChild( $child = Tree::Simple->new($container) );
395 $self->_find_or_create_namespace_node( $child, @namespace );
398 =head2 $self->setup_actions( $class, $context )
404 my ( $self, $c ) = @_;
406 $self->dispatch_types( [] );
407 $self->registered_dispatch_types( {} );
408 $self->method_action_class('Catalyst::Action');
409 $self->action_container_class('Catalyst::ActionContainer');
412 $self->do_load_dispatch_types( @{ $self->preload_dispatch_types } );
413 @{ $self->registered_dispatch_types }{@classes} = (1) x @classes;
415 foreach my $comp ( values %{ $c->components } ) {
416 $comp->register_actions($c) if $comp->can('register_actions');
419 $self->do_load_dispatch_types( @{ $self->postload_dispatch_types } );
421 return unless $c->debug;
423 my $privates = Text::SimpleTable->new(
431 my ( $walker, $parent, $prefix ) = @_;
432 $prefix .= $parent->getNodeValue || '';
433 $prefix .= '/' unless $prefix =~ /\/$/;
434 my $node = $parent->getNodeValue->actions;
436 for my $action ( keys %{$node} ) {
437 my $action_obj = $node->{$action};
439 if ( ( $action =~ /^_.*/ )
440 && ( !$c->config->{show_internal_actions} ) );
441 $privates->row( "$prefix$action", $action_obj->class, $action );
445 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
448 $walker->( $walker, $self->tree, '' );
449 $c->log->debug( "Loaded Private actions:\n" . $privates->draw )
452 # List all public actions
453 $_->list($c) for @{ $self->dispatch_types };
456 sub do_load_dispatch_types {
457 my ( $self, @types ) = @_;
461 # Preload action types
462 for my $type (@types) {
464 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
465 eval "require $class";
466 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
468 push @{ $self->dispatch_types }, $class->new;
470 push @loaded, $class;
478 Sebastian Riedel, C<sri@cpan.org>
479 Matt S Trout, C<mst@shadowcatsystems.co.uk>
483 This program is free software, you can redistribute it and/or modify it under
484 the same terms as Perl itself.