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