1 package Catalyst::Dispatcher;
5 with 'MooseX::Emulate::Class::Accessor::Fast';
7 use Catalyst::Exception;
10 use Catalyst::ActionContainer;
11 use Catalyst::DispatchType::Default;
12 use Catalyst::DispatchType::Index;
13 use Text::SimpleTable;
15 use Tree::Simple::Visitor::FindByPath;
18 #do these belong as package vars or should we build these via a builder method?
19 # Preload these action types
20 our @PRELOAD = qw/Index Path Regex/;
22 # Postload these action types
23 our @POSTLOAD = qw/Default/;
25 has _tree => (is => 'rw');
26 has _dispatch_types => (is => 'rw', default => sub { [] }, required => 1, lazy => 1);
27 has _registered_dispatch_types => (is => 'rw', default => sub { {} }, required => 1, lazy => 1);
28 has _method_action_class => (is => 'rw', default => 'Catalyst::Action');
29 has _action_container_class => (is => 'rw', default => 'Catalyst::ActionContainer');
31 has preload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@PRELOAD] });
32 has postload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@POSTLOAD] });
33 has _action_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
34 has _container_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
36 # Wrap accessors so you can assign a list and it will capture a list ref.
37 around qw/preload_dispatch_types postload_dispatch_types/ => sub {
40 return $self->$orig([@_]) if (scalar @_ && ref $_[0] ne 'ARRAY');
41 return $self->$orig(@_);
48 Catalyst::Dispatcher - The Catalyst Dispatcher
56 This is the class that maps public urls to actions in your Catalyst
57 application based on the attributes you set.
63 Construct a new dispatcher.
68 my ($self, $params) = @_;
71 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
73 $self->_tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
76 =head2 $self->preload_dispatch_types
78 An arrayref of pre-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->postload_dispatch_types
88 An arrayref of post-loaded dispatchtype classes
90 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
91 To use a custom class outside the regular C<Catalyst> namespace, prefix
92 it with a C<+>, like so:
96 =head2 $self->dispatch($c)
98 Delegate the dispatch to the action that matched the url, or return a
99 message about unknown resource
105 my ( $self, $c ) = @_;
106 if ( my $action = $c->action ) {
107 $c->forward( join( '/', '', $action->namespace, '_DISPATCH' ) );
111 my $path = $c->req->path;
113 ? qq/Unknown resource "$path"/
114 : "No default action defined";
115 $c->log->error($error) if $c->debug;
120 # $self->_command2action( $c, $command [, \@arguments ] )
121 # Search for an action, from the command and returns C<($action, $args)> on
122 # success. Returns C<(0)> on error.
124 sub _command2action {
125 my ( $self, $c, $command, @extra_params ) = @_;
128 $c->log->debug('Nothing to go to') if $c->debug;
134 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
135 @args = @{ pop @extra_params }
137 # this is a copy, it may take some abuse from
138 # ->_invoke_as_path if the path had trailing parts
139 @args = @{ $c->request->arguments };
144 # go to a string path ("/foo/bar/gorch")
146 if (Scalar::Util::blessed($command) && $command->isa('Catalyst::Action')) {
150 $action = $self->_invoke_as_path( $c, "$command", \@args );
153 # go to a component ( "MyApp::*::Foo" or $c->component("...")
154 # - a path or an object)
156 my $method = @extra_params ? $extra_params[0] : "process";
157 $action = $self->_invoke_as_component( $c, $command, $method );
160 return $action, \@args;
163 =head2 $self->visit( $c, $command [, \@arguments ] )
165 Documented in L<Catalyst>
171 $self->_do_visit('visit', @_);
177 my ( $c, $command ) = @_;
178 my ( $action, $args ) = $self->_command2action(@_);
179 my $error = qq/Couldn't $opname("$command"): /;
182 $error .= qq/Couldn't $opname to command "$command": /
183 .qq/Invalid action or component./;
185 elsif (!defined $action->namespace) {
186 $error .= qq/Action has no namespace: cannot $opname() to a plain /
187 .qq/method or component, must be a :Action or some sort./
189 elsif (!$action->class->can('_DISPATCH')) {
190 $error .= qq/Action cannot _DISPATCH. /
191 .qq/Did you try to $opname() a non-controller action?/;
199 $c->log->debug($error) if $c->debug;
203 $action = $self->expand_action($action);
205 local $c->request->{arguments} = $args;
206 local $c->{namespace} = $action->{'namespace'};
207 local $c->{action} = $action;
212 =head2 $self->go( $c, $command [, \@arguments ] )
214 Documented in L<Catalyst>
220 $self->_do_visit('go', @_);
224 =head2 $self->forward( $c, $command [, \@arguments ] )
226 Documented in L<Catalyst>
232 $self->_do_forward(forward => @_);
238 my ( $c, $command ) = @_;
239 my ( $action, $args ) = $self->_command2action(@_);
242 my $error .= qq/Couldn't $opname to command "$command": /
243 .qq/Invalid action or component./;
245 $c->log->debug($error) if $c->debug;
249 no warnings 'recursion';
251 my $orig_args = $c->request->arguments();
252 $c->request->arguments($args);
253 $action->dispatch( $c );
254 $c->request->arguments($orig_args);
259 =head2 $self->detach( $c, $command [, \@arguments ] )
261 Documented in L<Catalyst>
266 my ( $self, $c, $command, @args ) = @_;
267 $self->_do_forward(detach => $c, $command, @args ) if $command;
268 die $Catalyst::DETACH;
271 sub _action_rel2abs {
272 my ( $self, $c, $path ) = @_;
274 unless ( $path =~ m#^/# ) {
275 my $namespace = $c->stack->[-1]->namespace;
276 $path = "$namespace/$path";
283 sub _invoke_as_path {
284 my ( $self, $c, $rel_path, $args ) = @_;
286 my $path = $self->_action_rel2abs( $c, $rel_path );
288 my ( $tail, @extra_args );
289 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
290 { # allow $path to be empty
291 if ( my $action = $c->get_action( $tail, $path ) ) {
292 push @$args, @extra_args;
298 ; # if a match on the global namespace failed then the whole lookup failed
301 unshift @extra_args, $tail;
305 sub _find_component_class {
306 my ( $self, $c, $component ) = @_;
308 return ref($component)
309 || ref( $c->component($component) )
310 || $c->component($component);
313 sub _invoke_as_component {
314 my ( $self, $c, $component, $method ) = @_;
316 my $class = $self->_find_component_class( $c, $component ) || return 0;
318 if ( my $code = $class->can($method) ) {
319 return $self->_method_action_class->new(
323 reverse => "$class->$method",
325 namespace => Catalyst::Utils::class2prefix(
326 $class, $c->config->{case_sensitive}
333 qq/Couldn't forward to "$class". Does not implement "$method"/;
335 $c->log->debug($error)
341 =head2 $self->prepare_action($c)
343 Find an dispatch type that matches $c->req->path, and set args from it.
348 my ( $self, $c ) = @_;
350 my $path = $req->path;
351 my @path = split /\//, $req->path;
352 $req->args( \my @args );
354 unshift( @path, '' ); # Root action
356 DESCEND: while (@path) {
357 $path = join '/', @path;
360 $path = '' if $path eq '/'; # Root action
362 # Check out dispatch types to see if any will handle the path at
365 foreach my $type ( @{ $self->_dispatch_types } ) {
366 last DESCEND if $type->match( $c, $path );
369 # If not, move the last part path to args
370 my $arg = pop(@path);
371 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
375 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$req->captures||[]};
377 $c->log->debug( 'Path is "' . $req->match . '"' )
378 if ( $c->debug && defined $req->match && length $req->match );
380 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
381 if ( $c->debug && @args );
384 =head2 $self->get_action( $action, $namespace )
386 returns a named action from a given namespace.
391 my ( $self, $name, $namespace ) = @_;
394 $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
396 return $self->_action_hash->{"${namespace}/${name}"};
399 =head2 $self->get_action_by_path( $path );
401 Returns the named action by its full path.
405 sub get_action_by_path {
406 my ( $self, $path ) = @_;
408 $path = "/$path" unless $path =~ /\//;
409 $self->_action_hash->{$path};
412 =head2 $self->get_actions( $c, $action, $namespace )
417 my ( $self, $c, $action, $namespace ) = @_;
418 return [] unless $action;
420 $namespace = join( "/", grep { length } split '/', $namespace || "" );
422 my @match = $self->get_containers($namespace);
424 return map { $_->get_action($action) } @match;
427 =head2 $self->get_containers( $namespace )
429 Return all the action containers for a given namespace, inclusive
434 my ( $self, $namespace ) = @_;
436 $namespace = '' if $namespace eq '/';
440 if ( length $namespace ) {
442 push @containers, $self->_container_hash->{$namespace};
443 } while ( $namespace =~ s#/[^/]+$## );
446 return reverse grep { defined } @containers, $self->_container_hash->{''};
448 #return (split '/', $namespace); # isnt this more clear?
449 my @parts = split '/', $namespace;
452 =head2 $self->uri_for_action($action, \@captures)
454 Takes a Catalyst::Action object and action parameters and returns a URI
455 part such that if $c->req->path were this URI part, this action would be
456 dispatched to with $c->req->captures set to the supplied arrayref.
458 If the action object is not available for external dispatch or the dispatcher
459 cannot determine an appropriate URI, this method will return undef.
464 my ( $self, $action, $captures) = @_;
466 foreach my $dispatch_type ( @{ $self->_dispatch_types } ) {
467 my $uri = $dispatch_type->uri_for_action( $action, $captures );
468 return( $uri eq '' ? '/' : $uri )
476 expand an action into a full representation of the dispatch.
477 mostly useful for chained, other actions will just return a
483 my ($self, $action) = @_;
485 foreach my $dispatch_type (@{ $self->_dispatch_types }) {
486 my $expanded = $dispatch_type->expand_action($action);
487 return $expanded if $expanded;
493 =head2 $self->register( $c, $action )
495 Make sure all required dispatch types for this action are loaded, then
496 pass the action to our dispatch types so they can register it if required.
497 Also, set up the tree with the action containers.
502 my ( $self, $c, $action ) = @_;
504 my $registered = $self->_registered_dispatch_types;
506 #my $priv = 0; #seems to be unused
507 foreach my $key ( keys %{ $action->attributes } ) {
508 next if $key eq 'Private';
509 my $class = "Catalyst::DispatchType::$key";
510 unless ( $registered->{$class} ) {
511 #some error checking rethrowing here wouldn't hurt.
512 eval { Class::MOP::load_class($class) };
513 push( @{ $self->_dispatch_types }, $class->new ) unless $@;
514 $registered->{$class} = 1;
518 # Pass the action to our dispatch types so they can register it if reqd.
519 foreach my $type ( @{ $self->_dispatch_types } ) {
520 $type->register( $c, $action );
523 my $namespace = $action->namespace;
524 my $name = $action->name;
526 my $container = $self->_find_or_create_action_container($namespace);
528 # Set the method value
529 $container->add_action($action);
531 $self->_action_hash->{"$namespace/$name"} = $action;
532 $self->_container_hash->{$namespace} = $container;
535 sub _find_or_create_action_container {
536 my ( $self, $namespace ) = @_;
538 my $tree ||= $self->_tree;
540 return $tree->getNodeValue unless $namespace;
542 my @namespace = split '/', $namespace;
543 return $self->_find_or_create_namespace_node( $tree, @namespace )
547 sub _find_or_create_namespace_node {
548 my ( $self, $parent, $part, @namespace ) = @_;
550 return $parent unless $part;
553 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
556 my $container = Catalyst::ActionContainer->new($part);
557 $parent->addChild( $child = Tree::Simple->new($container) );
560 $self->_find_or_create_namespace_node( $child, @namespace );
563 =head2 $self->setup_actions( $class, $context )
569 my ( $self, $c ) = @_;
573 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
574 @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
576 foreach my $comp ( values %{ $c->components } ) {
577 $comp->register_actions($c) if $comp->can('register_actions');
580 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
582 return unless $c->debug;
584 my $privates = Text::SimpleTable->new(
592 my ( $walker, $parent, $prefix ) = @_;
593 $prefix .= $parent->getNodeValue || '';
594 $prefix .= '/' unless $prefix =~ /\/$/;
595 my $node = $parent->getNodeValue->actions;
597 for my $action ( keys %{$node} ) {
598 my $action_obj = $node->{$action};
600 if ( ( $action =~ /^_.*/ )
601 && ( !$c->config->{show_internal_actions} ) );
602 $privates->row( "$prefix$action", $action_obj->class, $action );
606 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
609 $walker->( $walker, $self->_tree, '' );
610 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
613 # List all public actions
614 $_->list($c) for @{ $self->_dispatch_types };
617 sub _load_dispatch_types {
618 my ( $self, @types ) = @_;
622 # Preload action types
623 for my $type (@types) {
625 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
627 eval { Class::MOP::load_class($class) };
628 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
630 push @{ $self->_dispatch_types }, $class->new;
632 push @loaded, $class;
639 __PACKAGE__->meta->make_immutable;
647 Catalyst Contributors, see Catalyst.pm
651 This program is free software, you can redistribute it and/or modify it under
652 the same terms as Perl itself.