Commit | Line | Data |
480d94b5 |
1 | =encoding UTF-8 |
2 | |
3 | =head1 Name |
4 | |
5 | Catalyst::RouteMatching - How Catalyst maps an incoming URL to actions in controllers. |
6 | |
7 | =head1 Description |
8 | |
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) |
11 | in a controller. |
12 | |
2234c98a |
13 | =head2 Request to Controller/Action Matching |
14 | |
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: |
17 | |
18 | package MyApp::Controller::Foo; |
19 | |
20 | use Moose; |
21 | use MooseX::MethodAttributes |
22 | |
23 | extends 'Catalyst::Controller'; |
24 | |
25 | sub bar :Path('bar') Args(1) { ...} |
26 | sub baz :Path('bar/baz') Args(0) { ... } |
27 | |
79fb8f95 |
28 | Path length matches take precedence over all other types of matches (included HTTP |
2234c98a |
29 | Method, Scheme, etc.). The same holds true for Chained actions. Generally the |
30 | chain that matches the most PathParts wins. |
31 | |
32 | =head2 Args(N) versus Args |
33 | |
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. |
37 | |
38 | =head2 When two or more actions match a given Path |
39 | |
79fb8f95 |
40 | Sometimes two or more actions match the same path and all have the same PathPart |
2234c98a |
41 | length. For example: |
42 | |
43 | package MyApp::Controller::Root; |
44 | |
45 | use Moose; |
46 | use MooseX::MethodAttributes |
47 | |
48 | extends 'Catalyst::Controller'; |
49 | |
50 | sub root :Chained(/) CaptureArgs(0) { } |
51 | |
52 | sub one :Chained(root) PathPart('') Args(0) { } |
53 | sub two :Chained(root) PathPart('') Args(0) { } |
54 | sub three :Chained(root) PathPart('') Args(0) { } |
55 | |
56 | __PACKAGE__->meta->make_immutable; |
57 | |
58 | In this case the last defined action wins (for the example that is action 'three'). |
59 | |
60 | This is most common to happen when you are using action matching beyond paths, such as |
61 | when using method matching: |
62 | |
63 | package MyApp::Controller::Root; |
64 | |
65 | use Moose; |
66 | use MooseX::MethodAttributes |
67 | |
68 | extends 'Catalyst::Controller'; |
69 | |
70 | sub root :Chained(/) CaptureArgs(0) { } |
71 | |
72 | sub any :Chained(root) PathPart('') Args(0) { } |
73 | sub get :GET Chained(root) PathPart('') Args(0) { } |
74 | |
75 | __PACKAGE__->meta->make_immutable; |
76 | |
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. |
79 | |
480d94b5 |
80 | =head2 Type Constraints in Args and Capture Args |
81 | |
82 | Beginning in Version 5.90090+ you may use L<Moose>, L<MooseX::Types> or L<Type::Tiny> |
79fb8f95 |
83 | type constraints to further declare allowed matching for Args or CaptureArgs. Here |
480d94b5 |
84 | is a simple example: |
85 | |
86 | package MyApp::Controller::User; |
87 | |
88 | use Moose; |
89 | use MooseX::MethodAttributes; |
6a226ee3 |
90 | use MooseX::Types::Moose qw(Int); |
480d94b5 |
91 | |
92 | extends 'Catalyst::Controller'; |
93 | |
6a226ee3 |
94 | sub find :Path('') Args(Int) { |
480d94b5 |
95 | my ($self, $c, $int) = @_; |
96 | } |
97 | |
98 | __PACKAGE__->meta->make_immutable; |
99 | |
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 |
d249a614 |
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 |
480d94b5 |
106 | exception, prompting you to add additional checking of the argument prior to using it. |
d249a614 |
107 | In general it is hoped this feature can lead to reduced validation boilerplate and more |
108 | easily understood and declarative actions. |
480d94b5 |
109 | |
110 | More than one argument may be added by comma separating your type constraint names, for |
111 | example: |
112 | |
75ce30d0 |
113 | use Types::Standard qw/Int Str/; |
114 | |
480d94b5 |
115 | sub find :Path('') Args(Int,Int,Str) { |
116 | my ($self, $c, $int1, $int2, $str) = @_; |
117 | } |
118 | |
75ce30d0 |
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). |
123 | |
124 | Conversely, you should not quote types that are imported! |
480d94b5 |
125 | |
126 | =head3 Using type constraints in a controller |
127 | |
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 |
d249a614 |
130 | and export them to a controller that wishes to use them. We recommend using L<Type::Tiny> or |
480d94b5 |
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>: |
133 | |
134 | package MyApp::Controller::User; |
135 | |
136 | use Moose; |
137 | use MooseX::MethodAttributes; |
75ce30d0 |
138 | use Types::Standard qw/StrMatch Int/; |
480d94b5 |
139 | |
140 | extends 'Catalyst::Controller'; |
141 | |
142 | sub looks_like_a_date :Path('') Args(StrMatch[qr{\d\d-\d\d-\d\d}]) { |
143 | my ($self, $c, $int) = @_; |
144 | } |
145 | |
146 | __PACKAGE__->meta->make_immutable; |
147 | |
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. |
150 | |
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. |
154 | |
59051400 |
155 | =head3 Type constraint namespace. |
156 | |
157 | By default we assume the namespace which defines the type constraint is in the package |
158 | which contains the action declaring the arg or capture arg. However if you do not wish |
159 | to import type constraints into you package, you may use a fully qualified namespace for |
160 | your type constraint. If you do this you must install L<Type::Tiny> which defines the |
161 | code used to lookup and normalize the various types of Type constraint libraries. |
162 | |
163 | Example: |
164 | |
165 | package MyApp::Example; |
166 | |
167 | use Moose; |
168 | use MooseX::MethodAttributes; |
169 | |
170 | extends 'Catalyst::Controller'; |
171 | |
172 | sub an_int_ns :Local Args(MyApp::Types::Int) { |
173 | my ($self, $c, $int) = @_; |
174 | $c->res->body('an_int (withrole)'); |
175 | } |
176 | |
177 | Would basically work the same as: |
178 | |
179 | package MyApp::Example; |
180 | |
181 | use Moose; |
182 | use MooseX::MethodAttributes; |
183 | use MyApp::Types 'Int'; |
184 | |
185 | extends 'Catalyst::Controller'; |
186 | |
187 | sub an_int_ns :Local Args(Int) { |
188 | my ($self, $c, $int) = @_; |
189 | $c->res->body('an_int (withrole)'); |
190 | } |
191 | |
192 | =head3 namespace::autoclean |
193 | |
194 | If you want to use L<namespace::autoclean> in your controllers you must 'except' imported |
195 | type constraints since the code that resolves type constraints in args / capture args |
196 | run after the cleaning. For example: |
197 | |
198 | package MyApp::Controller::Autoclean; |
199 | |
200 | use Moose; |
201 | use MooseX::MethodAttributes; |
202 | use namespace::autoclean -except => 'Int'; |
203 | use MyApp::Types qw/Int/; |
204 | |
205 | extends 'Catalyst::Controller'; |
206 | |
207 | sub an_int :Local Args(Int) { |
208 | my ($self, $c, $int) = @_; |
209 | $c->res->body('an_int (autoclean)'); |
210 | } |
211 | |
212 | =head3 Using roles and base controller with type constraints |
213 | |
214 | If your controller is using a base class or a role that has an action with a type constraint |
215 | you should declare your use of the type constraint in that role or base controller in the |
216 | same way as you do in main controllers. Catalyst will try to find the package with declares |
217 | the type constraint first by looking in any roles and then in superclasses. It will use the |
218 | first package that defines the type constraint. For example: |
219 | |
220 | package MyApp::Role; |
221 | |
222 | use Moose::Role; |
223 | use MooseX::MethodAttributes::Role; |
224 | use MyApp::Types qw/Int/; |
225 | |
226 | sub an_int :Local Args(Int) { |
227 | my ($self, $c, $int) = @_; |
228 | $c->res->body('an_int (withrole)'); |
229 | } |
230 | |
231 | sub an_int_ns :Local Args(MyApp::Types::Int) { |
232 | my ($self, $c, $int) = @_; |
233 | $c->res->body('an_int (withrole)'); |
234 | } |
235 | |
236 | package MyApp::BaseController; |
237 | |
238 | use Moose; |
239 | use MooseX::MethodAttributes; |
240 | use MyApp::Types qw/Int/; |
241 | |
242 | extends 'Catalyst::Controller'; |
243 | |
244 | sub from_parent :Local Args(Int) { |
245 | my ($self, $c, $id) = @_; |
246 | $c->res->body('from_parent $id'); |
247 | } |
248 | |
249 | package MyApp::Controller::WithRole; |
250 | |
251 | use Moose; |
252 | use MooseX::MethodAttributes; |
253 | |
254 | extends 'MyApp::BaseController'; |
255 | |
256 | with 'MyApp::Role'; |
257 | |
258 | If you have complex controller hierarchy, we |
259 | do not at this time attempt to look for all packages with a match type constraint, but instead |
260 | take the first one found. In the future we may add code that attempts to insure a sane use |
261 | of subclasses with type constraints but right now there are no clear use cases so report issues |
262 | and interests. |
263 | |
480d94b5 |
264 | =head3 Match order when more than one Action matches a path. |
265 | |
266 | As previously described, L<Catalyst> will match 'the longest path', which generally means |
79fb8f95 |
267 | that named path / path_parts will take precedence over Args or CaptureArgs. However, what |
480d94b5 |
268 | will happen if two actions match the same path with equal args? For example: |
269 | |
270 | sub an_int :Path(user) Args(Int) { |
271 | } |
272 | |
273 | sub an_any :Path(user) Args(1) { |
274 | } |
275 | |
276 | In this case L<Catalyst> will check actions starting from the LAST one defined. Generally |
277 | this means you should put your most specific action rules LAST and your 'catch-alls' first. |
278 | In the above example, since Args(1) will match any argument, you will find that that 'an_int' |
279 | action NEVER gets hit. You would need to reverse the order: |
280 | |
281 | sub an_any :Path(user) Args(1) { |
282 | } |
283 | |
284 | sub an_int :Path(user) Args(Int) { |
285 | } |
286 | |
d249a614 |
287 | Now requests that match this path would first hit the 'an_int' action and will check to see if |
288 | the argument is an integer. If it is, then the action will execute, otherwise it will pass and |
79fb8f95 |
289 | the dispatcher will check the next matching action (in this case we fall through to the 'an_any' |
d249a614 |
290 | action). |
480d94b5 |
291 | |
292 | =head3 Type Constraints and Chained Actions |
293 | |
294 | Using type constraints in Chained actions works the same as it does for Path and Local or Global |
d249a614 |
295 | actions. The only difference is that you may declare type constraints on CaptureArgs as |
480d94b5 |
296 | well as Args. For Example: |
297 | |
75ce30d0 |
298 | use Types::Standard qw/Int Tuple/; |
299 | |
480d94b5 |
300 | sub chain_base :Chained(/) CaptureArgs(1) { } |
301 | |
9228a8ec |
302 | sub any_priority_chain :GET Chained(chain_base) PathPart('') Args(1) { } |
480d94b5 |
303 | |
304 | sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { } |
305 | |
306 | sub link_any :Chained(chain_base) PathPart('') CaptureArgs(1) { } |
307 | |
308 | sub any_priority_link_any :Chained(link_any) PathPart('') Args(1) { } |
309 | |
9228a8ec |
310 | sub int_priority_link_any :Chained(link_any) PathPart('') Args(Int) { } |
480d94b5 |
311 | |
312 | sub link_int :Chained(chain_base) PathPart('') CaptureArgs(Int) { } |
313 | |
9228a8ec |
314 | sub any_priority_link :Chained(link_int) PathPart('') Args(1) { } |
480d94b5 |
315 | |
9228a8ec |
316 | sub int_priority_link :Chained(link_int) PathPart('') Args(Int) { } |
317 | |
318 | sub link_int_int :Chained(chain_base) PathPart('') CaptureArgs(Int,Int) { } |
319 | |
320 | sub any_priority_link2 :Chained(link_int_int) PathPart('') Args(1) { } |
321 | |
322 | sub int_priority_link2 :Chained(link_int_int) PathPart('') Args(Int) { } |
323 | |
324 | sub link_tuple :Chained(chain_base) PathPart('') CaptureArgs(Tuple[Int,Int,Int]) { } |
325 | |
326 | sub any_priority_link3 :Chained(link_tuple) PathPart('') Args(1) { } |
327 | |
328 | sub int_priority_link3 :Chained(link_tuple) PathPart('') Args(Int) { } |
480d94b5 |
329 | |
79fb8f95 |
330 | These chained actions might create match tables like the following: |
480d94b5 |
331 | |
332 | [debug] Loaded Chained actions: |
9228a8ec |
333 | .-------------------------------------+--------------------------------------. |
334 | | Path Spec | Private | |
335 | +-------------------------------------+--------------------------------------+ |
336 | | /chain_base/*/* | /chain_base (1) | |
337 | | | => GET /any_priority_chain (1) | |
338 | | /chain_base/*/*/* | /chain_base (1) | |
339 | | | -> /link_int (Int) | |
340 | | | => /any_priority_link (1) | |
341 | | /chain_base/*/*/*/* | /chain_base (1) | |
342 | | | -> /link_int_int (Int,Int) | |
343 | | | => /any_priority_link2 (1) | |
344 | | /chain_base/*/*/*/*/* | /chain_base (1) | |
345 | | | -> /link_tuple (Tuple[Int,Int,Int]) | |
346 | | | => /any_priority_link3 (1) | |
347 | | /chain_base/*/*/* | /chain_base (1) | |
348 | | | -> /link_any (1) | |
349 | | | => /any_priority_link_any (1) | |
350 | | /chain_base/*/*/*/*/*/* | /chain_base (1) | |
351 | | | -> /link_tuple (Tuple[Int,Int,Int]) | |
352 | | | -> /link2_int (UserId) | |
353 | | | => GET /finally (Int) | |
354 | | /chain_base/*/*/*/*/*/... | /chain_base (1) | |
355 | | | -> /link_tuple (Tuple[Int,Int,Int]) | |
356 | | | -> /link2_int (UserId) | |
357 | | | => GET /finally2 (...) | |
358 | | /chain_base/*/* | /chain_base (1) | |
359 | | | => /int_priority_chain (Int) | |
360 | | /chain_base/*/*/* | /chain_base (1) | |
361 | | | -> /link_int (Int) | |
362 | | | => /int_priority_link (Int) | |
363 | | /chain_base/*/*/*/* | /chain_base (1) | |
364 | | | -> /link_int_int (Int,Int) | |
365 | | | => /int_priority_link2 (Int) | |
366 | | /chain_base/*/*/*/*/* | /chain_base (1) | |
367 | | | -> /link_tuple (Tuple[Int,Int,Int]) | |
368 | | | => /int_priority_link3 (Int) | |
369 | | /chain_base/*/*/* | /chain_base (1) | |
370 | | | -> /link_any (1) | |
371 | | | => /int_priority_link_any (Int) | |
372 | '-------------------------------------+--------------------------------------' |
480d94b5 |
373 | |
374 | As you can see the same general path could be matched by various action chains. In this case |
375 | the rule described in the previous section should be followed, which is that L<Catalyst> |
376 | will start with the last defined action and work upward. For example the action C<int_priority_chain> |
377 | would be checked before C<any_priority_chain>. The same applies for actions that are midway links |
378 | in a longer chain. In this case C<link_int> would be checked before C<link_any>. So as always we |
79fb8f95 |
379 | recommend that you place you priority or most constrained actions last and you least or catch-all |
480d94b5 |
380 | actions first. |
381 | |
382 | Although this reverse order checking may seen counter intuitive it does have the added benefit that |
383 | when inheriting controllers any new actions added would take check precedence over those in your |
384 | parent controller or consumed role. |
385 | |
9228a8ec |
386 | Please note that your declared type constraint names will now appear in the debug console. |
387 | |
480d94b5 |
388 | =head1 Author |
389 | |
390 | John Napiorkowski L<jjnapiork@cpan.org|email:jjnapiork@cpan.org> |
391 | |
392 | =cut |
393 | |