1Catalyst::RouteMatchingU(s3e)r Contributed Perl DocumentaCtaitoanlyst::RouteMatching(3)
2
3
4

Name

6       Catalyst::RouteMatching - How Catalyst maps an incoming URL to actions
7       in controllers.
8

Description

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

Author

409       John Napiorkowski jjnapiork@cpan.org <email:jjnapiork@cpan.org>
410
411
412
413perl v5.34.0                      2022-01-21        Catalyst::RouteMatching(3)
Impressum