++ed by:
SKAUFMAN RODRIGO STAUGAARD VTFRVL JROBINSON

69 PAUSE users
62 non-PAUSE users.

John Napiorkowski
and 1 contributors

Name

Catalyst::RouteMatching - How Catalyst maps an incoming URL to actions in controllers.

Description

This is a WIP document intended to help people understand the logic that Catalyst uses to determine how to match in incoming request to an action (or action chain) in a controller.

Type Constraints in Args and Capture Args

Beginning in Version 5.90090+ you may use Moose, MooseX::Types or Type::Tiny type constraints to futher declare allowed matching for Args or CaptureArgs. Here is a simple example:

    package MyApp::Controller::User;

    use Moose;
    use MooseX::MethodAttributes;

    extends 'Catalyst::Controller';

    sub find :Path('') Args(Int) {
      my ($self, $c, $int) = @_;
    }

    __PACKAGE__->meta->make_immutable;

In this case the incoming request "http://localhost:/user/100" would match the action find but "http://localhost:/user/not_a_number" would not. You may find declaring constraints in this manner aids with debugging, automatic generation of documentation and reducing the amount of manual checking you might need to do in your actions. For example if the argument in the given action was going to be used to lookup a row in a database, if the matching field expected an integer, a string might cause a database exception, prompting you to add additional checking of the argument prior to using it. In general it is hoped this feature can lead to reduced validation boilerplate and more easily understood and declarative actions.

More than one argument may be added by comma separating your type constraint names, for example:

    sub find :Path('') Args(Int,Int,Str) {
      my ($self, $c, $int1, $int2, $str) = @_;
    }

Would require three arguments, an integer, integer and a string.

Using type constraints in a controller

By default Catalyst allows all the standard, built-in, named type constraints that come bundled with Moose. However it is trivial to create your own Type constraint libraries and export them to a controller that wishes to use them. We recommend using Type::Tiny or MooseX::Types for this. Here is an example using some extended type constraints via the Types::Standard library that is packaged with Type::Tiny:

    package MyApp::Controller::User;

    use Moose;
    use MooseX::MethodAttributes;
    use Types::Standard qw/StrMatch/;
    
    extends 'Catalyst::Controller';

    sub looks_like_a_date :Path('') Args(StrMatch[qr{\d\d-\d\d-\d\d}]) {
      my ($self, $c, $int) = @_;
    }

    __PACKAGE__->meta->make_immutable;

This would match URLs like "http://localhost/user/11-11-2015" for example. If you've been missing the old RegExp matching, this can emulate a good chunk of that ability, and more.

A tutorial on how to make custom type libraries is outside the scope of this document. I'd recommend looking at the copious documentation in Type::Tiny or in MooseX::Types if you prefer that system. The author recommends Type::Tiny if you are unsure which to use.

Match order when more than one Action matches a path.

As previously described, Catalyst will match 'the longest path', which generally means that named path / path_parts will take precidence over Args or CaptureArgs. However, what will happen if two actions match the same path with equal args? For example:

    sub an_int :Path(user) Args(Int) {
    }

    sub an_any :Path(user) Args(1) {
    }

In this case Catalyst will check actions starting from the LAST one defined. Generally this means you should put your most specific action rules LAST and your 'catch-alls' first. In the above example, since Args(1) will match any argument, you will find that that 'an_int' action NEVER gets hit. You would need to reverse the order:

    sub an_any :Path(user) Args(1) {
    }

    sub an_int :Path(user) Args(Int) {
    }

Now requests that match this path would first hit the 'an_int' action and will check to see if the argument is an integer. If it is, then the action will execute, otherwise it will pass and the dispatcher will check the next matching action (in this case we fall thru to the 'an_any' action).

Type Constraints and Chained Actions

Using type constraints in Chained actions works the same as it does for Path and Local or Global actions. The only difference is that you may declare type constraints on CaptureArgs as well as Args. For Example:

  sub chain_base :Chained(/) CaptureArgs(1) { }

    sub any_priority_chain :GET Chained(chain_base) PathPart('') Args(1) {  }

    sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) {  }

    sub link_any :Chained(chain_base) PathPart('') CaptureArgs(1) { }

      sub any_priority_link_any :Chained(link_any) PathPart('') Args(1) {  }

      sub int_priority_link_any :Chained(link_any) PathPart('') Args(Int) {  }
    
    sub link_int :Chained(chain_base) PathPart('') CaptureArgs(Int) { }

      sub any_priority_link :Chained(link_int) PathPart('') Args(1) {  }

      sub int_priority_link :Chained(link_int) PathPart('') Args(Int) {  }

    sub link_int_int :Chained(chain_base) PathPart('') CaptureArgs(Int,Int) { }

      sub any_priority_link2 :Chained(link_int_int) PathPart('') Args(1) {  }

      sub int_priority_link2 :Chained(link_int_int) PathPart('') Args(Int) {  }

    sub link_tuple :Chained(chain_base) PathPart('') CaptureArgs(Tuple[Int,Int,Int]) { }

      sub any_priority_link3 :Chained(link_tuple) PathPart('') Args(1) {  }

      sub int_priority_link3 :Chained(link_tuple) PathPart('') Args(Int) {  }

These chained actions migth create match tables like the following:

    [debug] Loaded Chained actions:
    .-------------------------------------+--------------------------------------.
    | Path Spec                           | Private                              |
    +-------------------------------------+--------------------------------------+
    | /chain_base/*/*                     | /chain_base (1)                      |
    |                                     | => GET /any_priority_chain (1)       |
    | /chain_base/*/*/*                   | /chain_base (1)                      |
    |                                     | -> /link_int (Int)                   |
    |                                     | => /any_priority_link (1)            |
    | /chain_base/*/*/*/*                 | /chain_base (1)                      |
    |                                     | -> /link_int_int (Int,Int)           |
    |                                     | => /any_priority_link2 (1)           |
    | /chain_base/*/*/*/*/*               | /chain_base (1)                      |
    |                                     | -> /link_tuple (Tuple[Int,Int,Int])  |
    |                                     | => /any_priority_link3 (1)           |
    | /chain_base/*/*/*                   | /chain_base (1)                      |
    |                                     | -> /link_any (1)                     |
    |                                     | => /any_priority_link_any (1)        |
    | /chain_base/*/*/*/*/*/*             | /chain_base (1)                      |
    |                                     | -> /link_tuple (Tuple[Int,Int,Int])  |
    |                                     | -> /link2_int (UserId)               |
    |                                     | => GET /finally (Int)                |
    | /chain_base/*/*/*/*/*/...           | /chain_base (1)                      |
    |                                     | -> /link_tuple (Tuple[Int,Int,Int])  |
    |                                     | -> /link2_int (UserId)               |
    |                                     | => GET /finally2 (...)               |
    | /chain_base/*/*                     | /chain_base (1)                      |
    |                                     | => /int_priority_chain (Int)         |
    | /chain_base/*/*/*                   | /chain_base (1)                      |
    |                                     | -> /link_int (Int)                   |
    |                                     | => /int_priority_link (Int)          |
    | /chain_base/*/*/*/*                 | /chain_base (1)                      |
    |                                     | -> /link_int_int (Int,Int)           |
    |                                     | => /int_priority_link2 (Int)         |
    | /chain_base/*/*/*/*/*               | /chain_base (1)                      |
    |                                     | -> /link_tuple (Tuple[Int,Int,Int])  |
    |                                     | => /int_priority_link3 (Int)         |
    | /chain_base/*/*/*                   | /chain_base (1)                      |
    |                                     | -> /link_any (1)                     |
    |                                     | => /int_priority_link_any (Int)      |
    '-------------------------------------+--------------------------------------'

As you can see the same general path could be matched by various action chains. In this case the rule described in the previous section should be followed, which is that Catalyst will start with the last defined action and work upward. For example the action int_priority_chain would be checked before any_priority_chain. The same applies for actions that are midway links in a longer chain. In this case link_int would be checked before link_any. So as always we recommend that you place you priority or most constrainted actions last and you least or catch-all actions first.

Although this reverse order checking may seen counter intuitive it does have the added benefit that when inheriting controllers any new actions added would take check precedence over those in your parent controller or consumed role.

Please note that your declared type constraint names will now appear in the debug console.

Conclusion

    TBD

Author

John Napiorkowski jjnapiork@cpan.org