5 Catalyst::RouteMatching - How Catalyst maps an incoming URL to actions in controllers.
9 This is a WIP document intended to help people understand the logic that L<Catalyst>
10 uses to determine how to match in incoming request to an action (or action chain)
13 =head2 Request to Controller/Action Matching
15 L<Catalyst> maps requests to action using a 'longest path wins' approach. That means
16 that if the request is '/foo/bar/baz' That means the action 'baz' matches:
18 package MyApp::Controller::Foo;
21 use MooseX::MethodAttributes
23 extends 'Catalyst::Controller';
25 sub bar :Path('bar') Args(1) { ...}
26 sub baz :Path('bar/baz') Args(0) { ... }
28 Path length matches take precidence over all other types of matches (included HTTP
29 Method, Scheme, etc.). The same holds true for Chained actions. Generally the
30 chain that matches the most PathParts wins.
32 =head2 Args(N) versus Args
34 'Args' matches any number of args. Because this functions as a sort of catchall, we
35 treat 'Args' as the lowest precedence of any Args(N) when N is 0 to infinity. An
36 action with 'Args' always get the last chance to match.
38 =head2 When two or more actions match a given Path
40 Sometimes two or more actions match the same path and all have the same pathpart
43 package MyApp::Controller::Root;
46 use MooseX::MethodAttributes
48 extends 'Catalyst::Controller';
50 sub root :Chained(/) CaptureArgs(0) { }
52 sub one :Chained(root) PathPart('') Args(0) { }
53 sub two :Chained(root) PathPart('') Args(0) { }
54 sub three :Chained(root) PathPart('') Args(0) { }
56 __PACKAGE__->meta->make_immutable;
58 In this case the last defined action wins (for the example that is action 'three').
60 This is most common to happen when you are using action matching beyond paths, such as
61 when using method matching:
63 package MyApp::Controller::Root;
66 use MooseX::MethodAttributes
68 extends 'Catalyst::Controller';
70 sub root :Chained(/) CaptureArgs(0) { }
72 sub any :Chained(root) PathPart('') Args(0) { }
73 sub get :GET Chained(root) PathPart('') Args(0) { }
75 __PACKAGE__->meta->make_immutable;
77 In the above example GET /root could match both actions. In this case you should define
78 your 'catchall' actions higher in the controller.
80 =head2 Type Constraints in Args and Capture Args
82 Beginning in Version 5.90090+ you may use L<Moose>, L<MooseX::Types> or L<Type::Tiny>
83 type constraints to futher declare allowed matching for Args or CaptureArgs. Here
86 package MyApp::Controller::User;
89 use MooseX::MethodAttributes;
90 use MooseX::Types::Moose qw(Int);
92 extends 'Catalyst::Controller';
94 sub find :Path('') Args(Int) {
95 my ($self, $c, $int) = @_;
98 __PACKAGE__->meta->make_immutable;
100 In this case the incoming request "http://localhost:/user/100" would match the action
101 C<find> but "http://localhost:/user/not_a_number" would not. You may find declaring
102 constraints in this manner aids with debugging, automatic generation of documentation
103 and reducing the amount of manual checking you might need to do in your actions. For
104 example if the argument in the given action was going to be used to lookup a row
105 in a database, if the matching field expected an integer, a string might cause a database
106 exception, prompting you to add additional checking of the argument prior to using it.
107 In general it is hoped this feature can lead to reduced validation boilerplate and more
108 easily understood and declarative actions.
110 More than one argument may be added by comma separating your type constraint names, for
113 use Types::Standard qw/Int Str/;
115 sub find :Path('') Args(Int,Int,Str) {
116 my ($self, $c, $int1, $int2, $str) = @_;
119 Would require three arguments, an integer, integer and a string. Note in this example we
120 constrained the args using imported types via L<Types::Standard>. Although you may use
121 stringy Moose types, we recommend imported types since this is less ambiguous to your readers.
122 If you want to use Moose stringy types. you must quote them (either "Int" or 'Int' is fine).
124 Conversely, you should not quote types that are imported!
126 =head3 Using type constraints in a controller
128 By default L<Catalyst> allows all the standard, built-in, named type constraints that come
129 bundled with L<Moose>. However it is trivial to create your own Type constraint libraries
130 and export them to a controller that wishes to use them. We recommend using L<Type::Tiny> or
131 L<MooseX::Types> for this. Here is an example using some extended type constraints via
132 the L<Types::Standard> library that is packaged with L<Type::Tiny>:
134 package MyApp::Controller::User;
137 use MooseX::MethodAttributes;
138 use Types::Standard qw/StrMatch Int/;
140 extends 'Catalyst::Controller';
142 sub looks_like_a_date :Path('') Args(StrMatch[qr{\d\d-\d\d-\d\d}]) {
143 my ($self, $c, $int) = @_;
146 __PACKAGE__->meta->make_immutable;
148 This would match URLs like "http://localhost/user/11-11-2015" for example. If you've been
149 missing the old RegExp matching, this can emulate a good chunk of that ability, and more.
151 A tutorial on how to make custom type libraries is outside the scope of this document. I'd
152 recommend looking at the copious documentation in L<Type::Tiny> or in L<MooseX::Types> if
153 you prefer that system. The author recommends L<Type::Tiny> if you are unsure which to use.
155 =head3 Match order when more than one Action matches a path.
157 As previously described, L<Catalyst> will match 'the longest path', which generally means
158 that named path / path_parts will take precidence over Args or CaptureArgs. However, what
159 will happen if two actions match the same path with equal args? For example:
161 sub an_int :Path(user) Args(Int) {
164 sub an_any :Path(user) Args(1) {
167 In this case L<Catalyst> will check actions starting from the LAST one defined. Generally
168 this means you should put your most specific action rules LAST and your 'catch-alls' first.
169 In the above example, since Args(1) will match any argument, you will find that that 'an_int'
170 action NEVER gets hit. You would need to reverse the order:
172 sub an_any :Path(user) Args(1) {
175 sub an_int :Path(user) Args(Int) {
178 Now requests that match this path would first hit the 'an_int' action and will check to see if
179 the argument is an integer. If it is, then the action will execute, otherwise it will pass and
180 the dispatcher will check the next matching action (in this case we fall thru to the 'an_any'
183 =head3 Type Constraints and Chained Actions
185 Using type constraints in Chained actions works the same as it does for Path and Local or Global
186 actions. The only difference is that you may declare type constraints on CaptureArgs as
187 well as Args. For Example:
189 use Types::Standard qw/Int Tuple/;
191 sub chain_base :Chained(/) CaptureArgs(1) { }
193 sub any_priority_chain :GET Chained(chain_base) PathPart('') Args(1) { }
195 sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { }
197 sub link_any :Chained(chain_base) PathPart('') CaptureArgs(1) { }
199 sub any_priority_link_any :Chained(link_any) PathPart('') Args(1) { }
201 sub int_priority_link_any :Chained(link_any) PathPart('') Args(Int) { }
203 sub link_int :Chained(chain_base) PathPart('') CaptureArgs(Int) { }
205 sub any_priority_link :Chained(link_int) PathPart('') Args(1) { }
207 sub int_priority_link :Chained(link_int) PathPart('') Args(Int) { }
209 sub link_int_int :Chained(chain_base) PathPart('') CaptureArgs(Int,Int) { }
211 sub any_priority_link2 :Chained(link_int_int) PathPart('') Args(1) { }
213 sub int_priority_link2 :Chained(link_int_int) PathPart('') Args(Int) { }
215 sub link_tuple :Chained(chain_base) PathPart('') CaptureArgs(Tuple[Int,Int,Int]) { }
217 sub any_priority_link3 :Chained(link_tuple) PathPart('') Args(1) { }
219 sub int_priority_link3 :Chained(link_tuple) PathPart('') Args(Int) { }
221 These chained actions migth create match tables like the following:
223 [debug] Loaded Chained actions:
224 .-------------------------------------+--------------------------------------.
225 | Path Spec | Private |
226 +-------------------------------------+--------------------------------------+
227 | /chain_base/*/* | /chain_base (1) |
228 | | => GET /any_priority_chain (1) |
229 | /chain_base/*/*/* | /chain_base (1) |
230 | | -> /link_int (Int) |
231 | | => /any_priority_link (1) |
232 | /chain_base/*/*/*/* | /chain_base (1) |
233 | | -> /link_int_int (Int,Int) |
234 | | => /any_priority_link2 (1) |
235 | /chain_base/*/*/*/*/* | /chain_base (1) |
236 | | -> /link_tuple (Tuple[Int,Int,Int]) |
237 | | => /any_priority_link3 (1) |
238 | /chain_base/*/*/* | /chain_base (1) |
239 | | -> /link_any (1) |
240 | | => /any_priority_link_any (1) |
241 | /chain_base/*/*/*/*/*/* | /chain_base (1) |
242 | | -> /link_tuple (Tuple[Int,Int,Int]) |
243 | | -> /link2_int (UserId) |
244 | | => GET /finally (Int) |
245 | /chain_base/*/*/*/*/*/... | /chain_base (1) |
246 | | -> /link_tuple (Tuple[Int,Int,Int]) |
247 | | -> /link2_int (UserId) |
248 | | => GET /finally2 (...) |
249 | /chain_base/*/* | /chain_base (1) |
250 | | => /int_priority_chain (Int) |
251 | /chain_base/*/*/* | /chain_base (1) |
252 | | -> /link_int (Int) |
253 | | => /int_priority_link (Int) |
254 | /chain_base/*/*/*/* | /chain_base (1) |
255 | | -> /link_int_int (Int,Int) |
256 | | => /int_priority_link2 (Int) |
257 | /chain_base/*/*/*/*/* | /chain_base (1) |
258 | | -> /link_tuple (Tuple[Int,Int,Int]) |
259 | | => /int_priority_link3 (Int) |
260 | /chain_base/*/*/* | /chain_base (1) |
261 | | -> /link_any (1) |
262 | | => /int_priority_link_any (Int) |
263 '-------------------------------------+--------------------------------------'
265 As you can see the same general path could be matched by various action chains. In this case
266 the rule described in the previous section should be followed, which is that L<Catalyst>
267 will start with the last defined action and work upward. For example the action C<int_priority_chain>
268 would be checked before C<any_priority_chain>. The same applies for actions that are midway links
269 in a longer chain. In this case C<link_int> would be checked before C<link_any>. So as always we
270 recommend that you place you priority or most constrainted actions last and you least or catch-all
273 Although this reverse order checking may seen counter intuitive it does have the added benefit that
274 when inheriting controllers any new actions added would take check precedence over those in your
275 parent controller or consumed role.
277 Please note that your declared type constraint names will now appear in the debug console.
281 John Napiorkowski L<jjnapiork@cpan.org|email:jjnapiork@cpan.org>