X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst%2FRouteMatching.pod;h=e5f567c77d324d6da4d343d5c22e3dd84521a0ea;hp=e155b28e04909183a2ed91cd29d5f45b9ba2aa78;hb=6a226ee3384511ecfe42baf99ed67f9b90469b70;hpb=d249a614f7a9e17f8aaa7b37f39c6563800c406e diff --git a/lib/Catalyst/RouteMatching.pod b/lib/Catalyst/RouteMatching.pod index e155b28..e5f567c 100644 --- a/lib/Catalyst/RouteMatching.pod +++ b/lib/Catalyst/RouteMatching.pod @@ -10,6 +10,73 @@ This is a WIP document intended to help people understand the logic that L maps requests to action using a 'longest path wins' approach. That means +that if the request is '/foo/bar/baz' That means the action 'baz' matches: + + package MyApp::Controller::Foo; + + use Moose; + use MooseX::MethodAttributes + + extends 'Catalyst::Controller'; + + sub bar :Path('bar') Args(1) { ...} + sub baz :Path('bar/baz') Args(0) { ... } + +Path length matches take precidence over all other types of matches (included HTTP +Method, Scheme, etc.). The same holds true for Chained actions. Generally the +chain that matches the most PathParts wins. + +=head2 Args(N) versus Args + +'Args' matches any number of args. Because this functions as a sort of catchall, we +treat 'Args' as the lowest precedence of any Args(N) when N is 0 to infinity. An +action with 'Args' always get the last chance to match. + +=head2 When two or more actions match a given Path + +Sometimes two or more actions match the same path and all have the same pathpart +length. For example: + + package MyApp::Controller::Root; + + use Moose; + use MooseX::MethodAttributes + + extends 'Catalyst::Controller'; + + sub root :Chained(/) CaptureArgs(0) { } + + sub one :Chained(root) PathPart('') Args(0) { } + sub two :Chained(root) PathPart('') Args(0) { } + sub three :Chained(root) PathPart('') Args(0) { } + + __PACKAGE__->meta->make_immutable; + +In this case the last defined action wins (for the example that is action 'three'). + +This is most common to happen when you are using action matching beyond paths, such as +when using method matching: + + package MyApp::Controller::Root; + + use Moose; + use MooseX::MethodAttributes + + extends 'Catalyst::Controller'; + + sub root :Chained(/) CaptureArgs(0) { } + + sub any :Chained(root) PathPart('') Args(0) { } + sub get :GET Chained(root) PathPart('') Args(0) { } + + __PACKAGE__->meta->make_immutable; + +In the above example GET /root could match both actions. In this case you should define +your 'catchall' actions higher in the controller. + =head2 Type Constraints in Args and Capture Args Beginning in Version 5.90090+ you may use L, L or L @@ -20,6 +87,7 @@ is a simple example: use Moose; use MooseX::MethodAttributes; + use MooseX::Types::Moose qw(Int); extends 'Catalyst::Controller'; @@ -42,11 +110,18 @@ easily understood and declarative actions. More than one argument may be added by comma separating your type constraint names, for example: + use Types::Standard qw/Int Str/; + sub find :Path('') Args(Int,Int,Str) { my ($self, $c, $int1, $int2, $str) = @_; } -Would require three arguments, an integer, integer and a string. +Would require three arguments, an integer, integer and a string. Note in this example we +constrained the args using imported types via L. Although you may use +stringy Moose types, we recommend imported types since this is less ambiguous to your readers. +If you want to use Moose stringy types. you must quote them (either "Int" or 'Int' is fine). + +Conversely, you should not quote types that are imported! =head3 Using type constraints in a controller @@ -60,7 +135,7 @@ the L library that is packaged with L: use Moose; use MooseX::MethodAttributes; - use Types::Standard qw/StrMatch/; + use Types::Standard qw/StrMatch Int/; extends 'Catalyst::Controller'; @@ -111,9 +186,11 @@ Using type constraints in Chained actions works the same as it does for Path and actions. The only difference is that you may declare type constraints on CaptureArgs as well as Args. For Example: + use Types::Standard qw/Int Tuple/; + sub chain_base :Chained(/) CaptureArgs(1) { } - sub any_priority_chain :Chained(chain_base) PathPart('') Args(1) { } + sub any_priority_chain :GET Chained(chain_base) PathPart('') Args(1) { } sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { } @@ -121,37 +198,69 @@ well as Args. For Example: sub any_priority_link_any :Chained(link_any) PathPart('') Args(1) { } - sub int_priority_link_any :Chained(link_any) PathPart('') Args(Int) { } + 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 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 int_priority_link :Chained(link_int) PathPart('') Args(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) | - | | => /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 | - '----------------------------------------------+----------------------------------------------' + .-------------------------------------+--------------------------------------. + | 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 L @@ -165,9 +274,7 @@ Although this reverse order checking may seen counter intuitive it does have the when inheriting controllers any new actions added would take check precedence over those in your parent controller or consumed role. -=head1 Conclusion - - TBD +Please note that your declared type constraint names will now appear in the debug console. =head1 Author