X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst%2FRouteMatching.pod;fp=lib%2FCatalyst%2FRouteMatching.pod;h=fb86ca00a8ac0afcd5e58ed15cf49fb9e0b82b60;hp=0000000000000000000000000000000000000000;hb=480d94b5f34577816d44fe05389ca5a085179363;hpb=a82c96cf2bf688f97140bad7fd3979a531416a22 diff --git a/lib/Catalyst/RouteMatching.pod b/lib/Catalyst/RouteMatching.pod new file mode 100644 index 0000000..fb86ca0 --- /dev/null +++ b/lib/Catalyst/RouteMatching.pod @@ -0,0 +1,173 @@ +=encoding UTF-8 + +=head1 Name + +Catalyst::RouteMatching - How Catalyst maps an incoming URL to actions in controllers. + +=head1 Description + +This is a WIP document intended to help people understand the logic that L +uses to determine how to match in incoming request to an action (or action chain) +in a controller. + +=head2 Type Constraints in Args and Capture Args + +Beginning in Version 5.90090+ you may use L, L or L +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 +C but "http://localhost:/user/not_a_number" would not. You may find declaring +constraints in this manner aids with debuggin, automatic generation of documentation +and reduces the amount of manual checking you might need to do in your actions. For +example if the argument in the example 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. + +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. + +=head3 Using type constraints in a controller + +By default L allows all the standard, built-in, named type constraints that come +bundled with L. However it is trivial to create your own Type constraint libraries +and export them to controller that wish to use them. We recommend using L or +L for this. Here is an example using some extended type constraints via +the L library that is packaged with L: + + 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 L or in L if +you prefer that system. The author recommends L if you are unsure which to use. + +=head3 Match order when more than one Action matches a path. + +As previously described, L 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 L 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 then the 'an_any' +action, which it likely what you are looking for! + +=head3 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 maybe declare type constraints on CaptureArgs as +well as Args. For Example: + + sub chain_base :Chained(/) CaptureArgs(1) { } + + sub any_priority_chain :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) { } + +These chained actions migth create match tables like the following: + + [debug] Loaded Chained actions: + .----------------------------------------------+----------------------------------------------. + | Path Spec | Private | + +----------------------------------------------+----------------------------------------------+ + | /chain_base/*/* | /chain_base (1) | + | | => /any_priority_chain | + | /chain_base/*/*/* | /chain_base (1) | + | | -> /link_int (1) | + | | => /any_priority_link | + | /chain_base/*/*/* | /chain_base (1) | + | | -> /link_any (1) | + | | => /any_priority_link_any | + | /chain_base/*/* | /chain_base (1) | + | | => /int_priority_chain | + | /chain_base/*/*/* | /chain_base (1) | + | | -> /link_int (1) | + | | => /int_priority_link | + | /chain_base/*/*/* | /chain_base (1) | + | | -> /link_any (1) | + | | => /int_priority_link_any | + '----------------------------------------------+----------------------------------------------' + +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 L +will start with the last defined action and work upward. For example the action C +would be checked before C. The same applies for actions that are midway links +in a longer chain. In this case C would be checked before C. 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. + +=head1 Conclusion + + TBD + +=head1 Author + +John Napiorkowski L + +=cut +