ntwk
ntwk

Reputation: 113

Dealing with ambiguous controller actions in Perl Catalyst

The problem

I have been learning the Catalyst web framework and I am trying to understand how Catalyst dispatches URLs to application "actions".

It seems I can write my application with ambiguous URL dispatching rules:

package Myapp::Controller::Root {
    __PACKAGE__->config(namespace => '');
    ....
    sub foo :Local {
        my ( $self, $c ) = @_;

        $c->response->body('foo action in Root controller.');
    }
}

package Myapp::Controller::Foo {
    ....
    sub default :Path {
        my ( $self, $c ) = @_;

        $c->response->body('default action in Foo controller.');
    }
}

These two actions are defined in different packages but both are associated with the '/foo/...' path. Looking at the above code, it's not clear which action will take precedence. That decision seems to be more or less made at random by Catalyst when the application is launched. Sometimes it is the former action, sometimes it's the latter.

Note that the above code is a contrived example and I probably wouldn't intentionally create two different actions that map to the same path. Nonetheless, I don't know how to predict which action will ultimately get called just by looking at the code.

The question

Is there a way to instruct Catalyst to disallow this kind of ambiguity or at least provide a warning?

Upvotes: 3

Views: 227

Answers (1)

xxfelixxx
xxfelixxx

Reputation: 6602

I'm not sure if there is an offical Catalyst way of doing this, but you could try the following, gently poached from a Catalyst question on how to list all actions in an app

Try adding something like the following to your App.pm just right after the __PACKAGE__->setup() line:

# Start the application
__PACKAGE__->setup();

# Get all known actions and store the action name and source controller
my $controller_map = {};
my @controllers = map { __PACKAGE__->controller($_) } __PACKAGE__->controllers;
for my $controller ( @controllers ) {
    my $controller_name = ref $controller;
    my @actions = $controller->get_action_methods;
    for my $action ( @actions ) {
        my $name = $action->name;
        next if $name =~ m|^_|; # _DISPATCH, _BEGIN, _AUTO, _ACTION, _END
        next if $name eq 'auto';
        next if $name eq 'begin';
        next if $name eq 'index';
        $controller_map->{ $name }->{ $controller_name }++;
    }
}

# Show all action names that come from more than 1 controller
my $bad_action_names = 0;
for my $action ( sort keys %$controller_map ) {
    my @original_controllers = keys %{ $controller_map->{ $action } };
    if ( scalar @original_controllers > 1 ) {
        $bad_action_names++;
        print "Bad Action Names for '$action' : " . join(' ', @original_controllers) . "\n";
    }
}

die "Unable to continue!!!" if $bad_action_names;

Upvotes: 1

Related Questions