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=9ca71abea125190da94fd91017f9dd71cfe9b636;hp=e91b5603d12d05d06c683a5e6a22d48283e9b83a;hb=HEAD;hpb=75ce30d0f208d49ead0134ab45fc2f45f72d6023 diff --git a/lib/Catalyst/RouteMatching.pod b/lib/Catalyst/RouteMatching.pod index e91b560..9ca71ab 100644 --- a/lib/Catalyst/RouteMatching.pod +++ b/lib/Catalyst/RouteMatching.pod @@ -10,20 +10,88 @@ 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 precedence 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 -type constraints to futher declare allowed matching for Args or CaptureArgs. Here +type constraints to further declare allowed matching for Args or CaptureArgs. Here is a simple example: package MyApp::Controller::User; use Moose; use MooseX::MethodAttributes; + use MooseX::Types::Moose qw(Int); extends 'Catalyst::Controller'; - sub find :Path('') Args('Int') { + sub find :Path('') Args(Int) { my ($self, $c, $int) = @_; } @@ -68,7 +136,7 @@ the L library that is packaged with L: use Moose; use MooseX::MethodAttributes; use Types::Standard qw/StrMatch Int/; - + extends 'Catalyst::Controller'; sub looks_like_a_date :Path('') Args(StrMatch[qr{\d\d-\d\d-\d\d}]) { @@ -84,10 +152,119 @@ A tutorial on how to make custom type libraries is outside the scope of this doc 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 Type constraint namespace. + +By default we assume the namespace which defines the type constraint is in the package +which contains the action declaring the arg or capture arg. However if you do not wish +to import type constraints into you package, you may use a fully qualified namespace for +your type constraint. If you do this you must install L which defines the +code used to lookup and normalize the various types of Type constraint libraries. + +Example: + + package MyApp::Example; + + use Moose; + use MooseX::MethodAttributes; + + extends 'Catalyst::Controller'; + + sub an_int_ns :Local Args(MyApp::Types::Int) { + my ($self, $c, $int) = @_; + $c->res->body('an_int (withrole)'); + } + +Would basically work the same as: + + package MyApp::Example; + + use Moose; + use MooseX::MethodAttributes; + use MyApp::Types 'Int'; + + extends 'Catalyst::Controller'; + + sub an_int_ns :Local Args(Int) { + my ($self, $c, $int) = @_; + $c->res->body('an_int (withrole)'); + } + +=head3 namespace::autoclean + +If you want to use L in your controllers you must 'except' imported +type constraints since the code that resolves type constraints in args / capture args +run after the cleaning. For example: + + package MyApp::Controller::Autoclean; + + use Moose; + use MooseX::MethodAttributes; + use namespace::autoclean -except => 'Int'; + use MyApp::Types qw/Int/; + + extends 'Catalyst::Controller'; + + sub an_int :Local Args(Int) { + my ($self, $c, $int) = @_; + $c->res->body('an_int (autoclean)'); + } + +=head3 Using roles and base controller with type constraints + +If your controller is using a base class or a role that has an action with a type constraint +you should declare your use of the type constraint in that role or base controller in the +same way as you do in main controllers. Catalyst will try to find the package with declares +the type constraint first by looking in any roles and then in superclasses. It will use the +first package that defines the type constraint. For example: + + package MyApp::Role; + + use Moose::Role; + use MooseX::MethodAttributes::Role; + use MyApp::Types qw/Int/; + + sub an_int :Local Args(Int) { + my ($self, $c, $int) = @_; + $c->res->body('an_int (withrole)'); + } + + sub an_int_ns :Local Args(MyApp::Types::Int) { + my ($self, $c, $int) = @_; + $c->res->body('an_int (withrole)'); + } + + package MyApp::BaseController; + + use Moose; + use MooseX::MethodAttributes; + use MyApp::Types qw/Int/; + + extends 'Catalyst::Controller'; + + sub from_parent :Local Args(Int) { + my ($self, $c, $id) = @_; + $c->res->body('from_parent $id'); + } + + package MyApp::Controller::WithRole; + + use Moose; + use MooseX::MethodAttributes; + + extends 'MyApp::BaseController'; + + with 'MyApp::Role'; + +If you have complex controller hierarchy, we +do not at this time attempt to look for all packages with a match type constraint, but instead +take the first one found. In the future we may add code that attempts to insure a sane use +of subclasses with type constraints but right now there are no clear use cases so report issues +and interests. + =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 +that named path / path_parts will take precedence 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) { @@ -109,7 +286,7 @@ action NEVER gets hit. You would need to reverse the order: 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' +the dispatcher will check the next matching action (in this case we fall through to the 'an_any' action). =head3 Type Constraints and Chained Actions @@ -119,7 +296,7 @@ actions. The only difference is that you may declare type constraints on Captur well as Args. For Example: use Types::Standard qw/Int Tuple/; - + sub chain_base :Chained(/) CaptureArgs(1) { } sub any_priority_chain :GET Chained(chain_base) PathPart('') Args(1) { } @@ -131,7 +308,7 @@ 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 link_int :Chained(chain_base) PathPart('') CaptureArgs(Int) { } sub any_priority_link :Chained(link_int) PathPart('') Args(1) { } @@ -150,7 +327,7 @@ well as Args. For Example: sub int_priority_link3 :Chained(link_tuple) PathPart('') Args(Int) { } -These chained actions migth create match tables like the following: +These chained actions might create match tables like the following: [debug] Loaded Chained actions: .-------------------------------------+--------------------------------------. @@ -199,7 +376,7 @@ the rule described in the previous section should be followed, which is that L 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 +recommend that you place you priority or most constrained 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 @@ -208,10 +385,6 @@ parent controller or consumed role. Please note that your declared type constraint names will now appear in the debug console. -=head1 Conclusion - - TBD - =head1 Author John Napiorkowski L