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