update doc a bit
[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
13=head2 Type Constraints in Args and Capture Args
14
15Beginning in Version 5.90090+ you may use L<Moose>, L<MooseX::Types> or L<Type::Tiny>
16type constraints to futher declare allowed matching for Args or CaptureArgs. Here
17is a simple example:
18
19 package MyApp::Controller::User;
20
21 use Moose;
22 use MooseX::MethodAttributes;
23
24 extends 'Catalyst::Controller';
25
26 sub find :Path('') Args(Int) {
27 my ($self, $c, $int) = @_;
28 }
29
30 __PACKAGE__->meta->make_immutable;
31
32In this case the incoming request "http://localhost:/user/100" would match the action
33C<find> but "http://localhost:/user/not_a_number" would not. You may find declaring
d249a614 34constraints in this manner aids with debugging, automatic generation of documentation
35and reducing the amount of manual checking you might need to do in your actions. For
36example if the argument in the given action was going to be used to lookup a row
37in a database, if the matching field expected an integer, a string might cause a database
480d94b5 38exception, prompting you to add additional checking of the argument prior to using it.
d249a614 39In general it is hoped this feature can lead to reduced validation boilerplate and more
40easily understood and declarative actions.
480d94b5 41
42More than one argument may be added by comma separating your type constraint names, for
43example:
44
45 sub find :Path('') Args(Int,Int,Str) {
46 my ($self, $c, $int1, $int2, $str) = @_;
47 }
48
49Would require three arguments, an integer, integer and a string.
50
51=head3 Using type constraints in a controller
52
53By default L<Catalyst> allows all the standard, built-in, named type constraints that come
54bundled with L<Moose>. However it is trivial to create your own Type constraint libraries
d249a614 55and export them to a controller that wishes to use them. We recommend using L<Type::Tiny> or
480d94b5 56L<MooseX::Types> for this. Here is an example using some extended type constraints via
57the L<Types::Standard> library that is packaged with L<Type::Tiny>:
58
59 package MyApp::Controller::User;
60
61 use Moose;
62 use MooseX::MethodAttributes;
63 use Types::Standard qw/StrMatch/;
64
65 extends 'Catalyst::Controller';
66
67 sub looks_like_a_date :Path('') Args(StrMatch[qr{\d\d-\d\d-\d\d}]) {
68 my ($self, $c, $int) = @_;
69 }
70
71 __PACKAGE__->meta->make_immutable;
72
73This would match URLs like "http://localhost/user/11-11-2015" for example. If you've been
74missing the old RegExp matching, this can emulate a good chunk of that ability, and more.
75
76A tutorial on how to make custom type libraries is outside the scope of this document. I'd
77recommend looking at the copious documentation in L<Type::Tiny> or in L<MooseX::Types> if
78you prefer that system. The author recommends L<Type::Tiny> if you are unsure which to use.
79
80=head3 Match order when more than one Action matches a path.
81
82As previously described, L<Catalyst> will match 'the longest path', which generally means
83that named path / path_parts will take precidence over Args or CaptureArgs. However, what
84will happen if two actions match the same path with equal args? For example:
85
86 sub an_int :Path(user) Args(Int) {
87 }
88
89 sub an_any :Path(user) Args(1) {
90 }
91
92In this case L<Catalyst> will check actions starting from the LAST one defined. Generally
93this means you should put your most specific action rules LAST and your 'catch-alls' first.
94In the above example, since Args(1) will match any argument, you will find that that 'an_int'
95action NEVER gets hit. You would need to reverse the order:
96
97 sub an_any :Path(user) Args(1) {
98 }
99
100 sub an_int :Path(user) Args(Int) {
101 }
102
d249a614 103Now requests that match this path would first hit the 'an_int' action and will check to see if
104the argument is an integer. If it is, then the action will execute, otherwise it will pass and
105the dispatcher will check the next matching action (in this case we fall thru to the 'an_any'
106action).
480d94b5 107
108=head3 Type Constraints and Chained Actions
109
110Using type constraints in Chained actions works the same as it does for Path and Local or Global
d249a614 111actions. The only difference is that you may declare type constraints on CaptureArgs as
480d94b5 112well as Args. For Example:
113
114 sub chain_base :Chained(/) CaptureArgs(1) { }
115
9228a8ec 116 sub any_priority_chain :GET Chained(chain_base) PathPart('') Args(1) { }
480d94b5 117
118 sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { }
119
120 sub link_any :Chained(chain_base) PathPart('') CaptureArgs(1) { }
121
122 sub any_priority_link_any :Chained(link_any) PathPart('') Args(1) { }
123
9228a8ec 124 sub int_priority_link_any :Chained(link_any) PathPart('') Args(Int) { }
480d94b5 125
126 sub link_int :Chained(chain_base) PathPart('') CaptureArgs(Int) { }
127
9228a8ec 128 sub any_priority_link :Chained(link_int) PathPart('') Args(1) { }
480d94b5 129
9228a8ec 130 sub int_priority_link :Chained(link_int) PathPart('') Args(Int) { }
131
132 sub link_int_int :Chained(chain_base) PathPart('') CaptureArgs(Int,Int) { }
133
134 sub any_priority_link2 :Chained(link_int_int) PathPart('') Args(1) { }
135
136 sub int_priority_link2 :Chained(link_int_int) PathPart('') Args(Int) { }
137
138 sub link_tuple :Chained(chain_base) PathPart('') CaptureArgs(Tuple[Int,Int,Int]) { }
139
140 sub any_priority_link3 :Chained(link_tuple) PathPart('') Args(1) { }
141
142 sub int_priority_link3 :Chained(link_tuple) PathPart('') Args(Int) { }
480d94b5 143
144These chained actions migth create match tables like the following:
145
146 [debug] Loaded Chained actions:
9228a8ec 147 .-------------------------------------+--------------------------------------.
148 | Path Spec | Private |
149 +-------------------------------------+--------------------------------------+
150 | /chain_base/*/* | /chain_base (1) |
151 | | => GET /any_priority_chain (1) |
152 | /chain_base/*/*/* | /chain_base (1) |
153 | | -> /link_int (Int) |
154 | | => /any_priority_link (1) |
155 | /chain_base/*/*/*/* | /chain_base (1) |
156 | | -> /link_int_int (Int,Int) |
157 | | => /any_priority_link2 (1) |
158 | /chain_base/*/*/*/*/* | /chain_base (1) |
159 | | -> /link_tuple (Tuple[Int,Int,Int]) |
160 | | => /any_priority_link3 (1) |
161 | /chain_base/*/*/* | /chain_base (1) |
162 | | -> /link_any (1) |
163 | | => /any_priority_link_any (1) |
164 | /chain_base/*/*/*/*/*/* | /chain_base (1) |
165 | | -> /link_tuple (Tuple[Int,Int,Int]) |
166 | | -> /link2_int (UserId) |
167 | | => GET /finally (Int) |
168 | /chain_base/*/*/*/*/*/... | /chain_base (1) |
169 | | -> /link_tuple (Tuple[Int,Int,Int]) |
170 | | -> /link2_int (UserId) |
171 | | => GET /finally2 (...) |
172 | /chain_base/*/* | /chain_base (1) |
173 | | => /int_priority_chain (Int) |
174 | /chain_base/*/*/* | /chain_base (1) |
175 | | -> /link_int (Int) |
176 | | => /int_priority_link (Int) |
177 | /chain_base/*/*/*/* | /chain_base (1) |
178 | | -> /link_int_int (Int,Int) |
179 | | => /int_priority_link2 (Int) |
180 | /chain_base/*/*/*/*/* | /chain_base (1) |
181 | | -> /link_tuple (Tuple[Int,Int,Int]) |
182 | | => /int_priority_link3 (Int) |
183 | /chain_base/*/*/* | /chain_base (1) |
184 | | -> /link_any (1) |
185 | | => /int_priority_link_any (Int) |
186 '-------------------------------------+--------------------------------------'
480d94b5 187
188As you can see the same general path could be matched by various action chains. In this case
189the rule described in the previous section should be followed, which is that L<Catalyst>
190will start with the last defined action and work upward. For example the action C<int_priority_chain>
191would be checked before C<any_priority_chain>. The same applies for actions that are midway links
192in a longer chain. In this case C<link_int> would be checked before C<link_any>. So as always we
193recommend that you place you priority or most constrainted actions last and you least or catch-all
194actions first.
195
196Although this reverse order checking may seen counter intuitive it does have the added benefit that
197when inheriting controllers any new actions added would take check precedence over those in your
198parent controller or consumed role.
199
9228a8ec 200Please note that your declared type constraint names will now appear in the debug console.
201
480d94b5 202=head1 Conclusion
203
204 TBD
205
206=head1 Author
207
208John Napiorkowski L<jjnapiork@cpan.org|email:jjnapiork@cpan.org>
209
210=cut
211