1Catalyst::RouteMatchingU(s3e)r Contributed Perl DocumentaCtaitoanlyst::RouteMatching(3)
2
3
4
6 Catalyst::RouteMatching - How Catalyst maps an incoming URL to actions
7 in controllers.
8
10 This is a WIP document intended to help people understand the logic
11 that Catalyst uses to determine how to match in incoming request to an
12 action (or action chain) in a controller.
13
14 Request to Controller/Action Matching
15 Catalyst maps requests to action using a 'longest path wins' approach.
16 That means that if the request is '/foo/bar/baz' That means the action
17 'baz' matches:
18
19 package MyApp::Controller::Foo;
20
21 use Moose;
22 use MooseX::MethodAttributes
23
24 extends 'Catalyst::Controller';
25
26 sub bar :Path('bar') Args(1) { ...}
27 sub baz :Path('bar/baz') Args(0) { ... }
28
29 Path length matches take precedence over all other types of matches
30 (included HTTP Method, Scheme, etc.). The same holds true for Chained
31 actions. Generally the chain that matches the most PathParts wins.
32
33 Args(N) versus Args
34 'Args' matches any number of args. Because this functions as a sort of
35 catchall, we treat 'Args' as the lowest precedence of any Args(N) when
36 N is 0 to infinity. An action with 'Args' always get the last chance
37 to match.
38
39 When two or more actions match a given Path
40 Sometimes two or more actions match the same path and all have the same
41 PathPart 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
59 action 'three').
60
61 This is most common to happen when you are using action matching beyond
62 paths, such as when using method matching:
63
64 package MyApp::Controller::Root;
65
66 use Moose;
67 use MooseX::MethodAttributes
68
69 extends 'Catalyst::Controller';
70
71 sub root :Chained(/) CaptureArgs(0) { }
72
73 sub any :Chained(root) PathPart('') Args(0) { }
74 sub get :GET Chained(root) PathPart('') Args(0) { }
75
76 __PACKAGE__->meta->make_immutable;
77
78 In the above example GET /root could match both actions. In this case
79 you should define your 'catchall' actions higher in the controller.
80
81 Type Constraints in Args and Capture Args
82 Beginning in Version 5.90090+ you may use Moose, MooseX::Types or
83 Type::Tiny type constraints to further declare allowed matching for
84 Args or CaptureArgs. Here is a simple example:
85
86 package MyApp::Controller::User;
87
88 use Moose;
89 use MooseX::MethodAttributes;
90 use MooseX::Types::Moose qw(Int);
91
92 extends 'Catalyst::Controller';
93
94 sub find :Path('') Args(Int) {
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
101 match the action "find" but "http://localhost:/user/not_a_number" would
102 not. You may find declaring constraints in this manner aids with
103 debugging, automatic generation of documentation and reducing the
104 amount of manual checking you might need to do in your actions. For
105 example if the argument in the given action was going to be used to
106 lookup a row in a database, if the matching field expected an integer,
107 a string might cause a database exception, prompting you to add
108 additional checking of the argument prior to using it. In general it
109 is hoped this feature can lead to reduced validation boilerplate and
110 more easily understood and declarative actions.
111
112 More than one argument may be added by comma separating your type
113 constraint names, for example:
114
115 use Types::Standard qw/Int Str/;
116
117 sub find :Path('') Args(Int,Int,Str) {
118 my ($self, $c, $int1, $int2, $str) = @_;
119 }
120
121 Would require three arguments, an integer, integer and a string. Note
122 in this example we constrained the args using imported types via
123 Types::Standard. Although you may use stringy Moose types, we
124 recommend imported types since this is less ambiguous to your readers.
125 If you want to use Moose stringy types. you must quote them (either
126 "Int" or 'Int' is fine).
127
128 Conversely, you should not quote types that are imported!
129
130 Using type constraints in a controller
131
132 By default Catalyst allows all the standard, built-in, named type
133 constraints that come bundled with Moose. However it is trivial to
134 create your own Type constraint libraries and export them to a
135 controller that wishes to use them. We recommend using Type::Tiny or
136 MooseX::Types for this. Here is an example using some extended type
137 constraints via the Types::Standard library that is packaged with
138 Type::Tiny:
139
140 package MyApp::Controller::User;
141
142 use Moose;
143 use MooseX::MethodAttributes;
144 use Types::Standard qw/StrMatch Int/;
145
146 extends 'Catalyst::Controller';
147
148 sub looks_like_a_date :Path('') Args(StrMatch[qr{\d\d-\d\d-\d\d}]) {
149 my ($self, $c, $int) = @_;
150 }
151
152 __PACKAGE__->meta->make_immutable;
153
154 This would match URLs like "http://localhost/user/11-11-2015" for
155 example. If you've been missing the old RegExp matching, this can
156 emulate a good chunk of that ability, and more.
157
158 A tutorial on how to make custom type libraries is outside the scope of
159 this document. I'd recommend looking at the copious documentation in
160 Type::Tiny or in MooseX::Types if you prefer that system. The author
161 recommends Type::Tiny if you are unsure which to use.
162
163 Type constraint namespace.
164
165 By default we assume the namespace which defines the type constraint is
166 in the package which contains the action declaring the arg or capture
167 arg. However if you do not wish to import type constraints into you
168 package, you may use a fully qualified namespace for your type
169 constraint. If you do this you must install Type::Tiny which defines
170 the code used to lookup and normalize the various types of Type
171 constraint libraries.
172
173 Example:
174
175 package MyApp::Example;
176
177 use Moose;
178 use MooseX::MethodAttributes;
179
180 extends 'Catalyst::Controller';
181
182 sub an_int_ns :Local Args(MyApp::Types::Int) {
183 my ($self, $c, $int) = @_;
184 $c->res->body('an_int (withrole)');
185 }
186
187 Would basically work the same as:
188
189 package MyApp::Example;
190
191 use Moose;
192 use MooseX::MethodAttributes;
193 use MyApp::Types 'Int';
194
195 extends 'Catalyst::Controller';
196
197 sub an_int_ns :Local Args(Int) {
198 my ($self, $c, $int) = @_;
199 $c->res->body('an_int (withrole)');
200 }
201
202 namespace::autoclean
203
204 If you want to use namespace::autoclean in your controllers you must
205 'except' imported type constraints since the code that resolves type
206 constraints in args / capture args run after the cleaning. For
207 example:
208
209 package MyApp::Controller::Autoclean;
210
211 use Moose;
212 use MooseX::MethodAttributes;
213 use namespace::autoclean -except => 'Int';
214 use MyApp::Types qw/Int/;
215
216 extends 'Catalyst::Controller';
217
218 sub an_int :Local Args(Int) {
219 my ($self, $c, $int) = @_;
220 $c->res->body('an_int (autoclean)');
221 }
222
223 Using roles and base controller with type constraints
224
225 If your controller is using a base class or a role that has an action
226 with a type constraint you should declare your use of the type
227 constraint in that role or base controller in the same way as you do in
228 main controllers. Catalyst will try to find the package with declares
229 the type constraint first by looking in any roles and then in
230 superclasses. It will use the first package that defines the type
231 constraint. For example:
232
233 package MyApp::Role;
234
235 use Moose::Role;
236 use MooseX::MethodAttributes::Role;
237 use MyApp::Types qw/Int/;
238
239 sub an_int :Local Args(Int) {
240 my ($self, $c, $int) = @_;
241 $c->res->body('an_int (withrole)');
242 }
243
244 sub an_int_ns :Local Args(MyApp::Types::Int) {
245 my ($self, $c, $int) = @_;
246 $c->res->body('an_int (withrole)');
247 }
248
249 package MyApp::BaseController;
250
251 use Moose;
252 use MooseX::MethodAttributes;
253 use MyApp::Types qw/Int/;
254
255 extends 'Catalyst::Controller';
256
257 sub from_parent :Local Args(Int) {
258 my ($self, $c, $id) = @_;
259 $c->res->body('from_parent $id');
260 }
261
262 package MyApp::Controller::WithRole;
263
264 use Moose;
265 use MooseX::MethodAttributes;
266
267 extends 'MyApp::BaseController';
268
269 with 'MyApp::Role';
270
271 If you have complex controller hierarchy, we do not at this time
272 attempt to look for all packages with a match type constraint, but
273 instead take the first one found. In the future we may add code that
274 attempts to insure a sane use of subclasses with type constraints but
275 right now there are no clear use cases so report issues and interests.
276
277 Match order when more than one Action matches a path.
278
279 As previously described, Catalyst will match 'the longest path', which
280 generally means that named path / path_parts will take precedence over
281 Args or CaptureArgs. However, what will happen if two actions match
282 the same path with equal args? For example:
283
284 sub an_int :Path(user) Args(Int) {
285 }
286
287 sub an_any :Path(user) Args(1) {
288 }
289
290 In this case Catalyst will check actions starting from the LAST one
291 defined. Generally this means you should put your most specific action
292 rules LAST and your 'catch-alls' first. In the above example, since
293 Args(1) will match any argument, you will find that that 'an_int'
294 action NEVER gets hit. You would need to reverse the order:
295
296 sub an_any :Path(user) Args(1) {
297 }
298
299 sub an_int :Path(user) Args(Int) {
300 }
301
302 Now requests that match this path would first hit the 'an_int' action
303 and will check to see if the argument is an integer. If it is, then
304 the action will execute, otherwise it will pass and the dispatcher will
305 check the next matching action (in this case we fall through to the
306 'an_any' action).
307
308 Type Constraints and Chained Actions
309
310 Using type constraints in Chained actions works the same as it does for
311 Path and Local or Global actions. The only difference is that you may
312 declare type constraints on CaptureArgs as well as Args. For Example:
313
314 use Types::Standard qw/Int Tuple/;
315
316 sub chain_base :Chained(/) CaptureArgs(1) { }
317
318 sub any_priority_chain :GET Chained(chain_base) PathPart('') Args(1) { }
319
320 sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { }
321
322 sub link_any :Chained(chain_base) PathPart('') CaptureArgs(1) { }
323
324 sub any_priority_link_any :Chained(link_any) PathPart('') Args(1) { }
325
326 sub int_priority_link_any :Chained(link_any) PathPart('') Args(Int) { }
327
328 sub link_int :Chained(chain_base) PathPart('') CaptureArgs(Int) { }
329
330 sub any_priority_link :Chained(link_int) PathPart('') Args(1) { }
331
332 sub int_priority_link :Chained(link_int) PathPart('') Args(Int) { }
333
334 sub link_int_int :Chained(chain_base) PathPart('') CaptureArgs(Int,Int) { }
335
336 sub any_priority_link2 :Chained(link_int_int) PathPart('') Args(1) { }
337
338 sub int_priority_link2 :Chained(link_int_int) PathPart('') Args(Int) { }
339
340 sub link_tuple :Chained(chain_base) PathPart('') CaptureArgs(Tuple[Int,Int,Int]) { }
341
342 sub any_priority_link3 :Chained(link_tuple) PathPart('') Args(1) { }
343
344 sub int_priority_link3 :Chained(link_tuple) PathPart('') Args(Int) { }
345
346 These chained actions might create match tables like the following:
347
348 [debug] Loaded Chained actions:
349 .-------------------------------------+--------------------------------------.
350 | Path Spec | Private |
351 +-------------------------------------+--------------------------------------+
352 | /chain_base/*/* | /chain_base (1) |
353 | | => GET /any_priority_chain (1) |
354 | /chain_base/*/*/* | /chain_base (1) |
355 | | -> /link_int (Int) |
356 | | => /any_priority_link (1) |
357 | /chain_base/*/*/*/* | /chain_base (1) |
358 | | -> /link_int_int (Int,Int) |
359 | | => /any_priority_link2 (1) |
360 | /chain_base/*/*/*/*/* | /chain_base (1) |
361 | | -> /link_tuple (Tuple[Int,Int,Int]) |
362 | | => /any_priority_link3 (1) |
363 | /chain_base/*/*/* | /chain_base (1) |
364 | | -> /link_any (1) |
365 | | => /any_priority_link_any (1) |
366 | /chain_base/*/*/*/*/*/* | /chain_base (1) |
367 | | -> /link_tuple (Tuple[Int,Int,Int]) |
368 | | -> /link2_int (UserId) |
369 | | => GET /finally (Int) |
370 | /chain_base/*/*/*/*/*/... | /chain_base (1) |
371 | | -> /link_tuple (Tuple[Int,Int,Int]) |
372 | | -> /link2_int (UserId) |
373 | | => GET /finally2 (...) |
374 | /chain_base/*/* | /chain_base (1) |
375 | | => /int_priority_chain (Int) |
376 | /chain_base/*/*/* | /chain_base (1) |
377 | | -> /link_int (Int) |
378 | | => /int_priority_link (Int) |
379 | /chain_base/*/*/*/* | /chain_base (1) |
380 | | -> /link_int_int (Int,Int) |
381 | | => /int_priority_link2 (Int) |
382 | /chain_base/*/*/*/*/* | /chain_base (1) |
383 | | -> /link_tuple (Tuple[Int,Int,Int]) |
384 | | => /int_priority_link3 (Int) |
385 | /chain_base/*/*/* | /chain_base (1) |
386 | | -> /link_any (1) |
387 | | => /int_priority_link_any (Int) |
388 '-------------------------------------+--------------------------------------'
389
390 As you can see the same general path could be matched by various action
391 chains. In this case the rule described in the previous section should
392 be followed, which is that Catalyst will start with the last defined
393 action and work upward. For example the action "int_priority_chain"
394 would be checked before "any_priority_chain". The same applies for
395 actions that are midway links in a longer chain. In this case
396 "link_int" would be checked before "link_any". So as always we
397 recommend that you place you priority or most constrained actions last
398 and you least or catch-all actions first.
399
400 Although this reverse order checking may seen counter intuitive it does
401 have the added benefit that when inheriting controllers any new actions
402 added would take check precedence over those in your parent controller
403 or consumed role.
404
405 Please note that your declared type constraint names will now appear in
406 the debug console.
407
409 John Napiorkowski jjnapiork@cpan.org <email:jjnapiork@cpan.org>
410
411
412
413perl v5.36.0 2023-01-20 Catalyst::RouteMatching(3)