Commit | Line | Data |
480d94b5 |
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 |
d249a614 |
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 |
480d94b5 |
38 | exception, prompting you to add additional checking of the argument prior to using it. |
d249a614 |
39 | In general it is hoped this feature can lead to reduced validation boilerplate and more |
40 | easily understood and declarative actions. |
480d94b5 |
41 | |
42 | More than one argument may be added by comma separating your type constraint names, for |
43 | example: |
44 | |
45 | sub find :Path('') Args(Int,Int,Str) { |
46 | my ($self, $c, $int1, $int2, $str) = @_; |
47 | } |
48 | |
49 | Would require three arguments, an integer, integer and a string. |
50 | |
51 | =head3 Using type constraints in a controller |
52 | |
53 | By default L<Catalyst> allows all the standard, built-in, named type constraints that come |
54 | bundled with L<Moose>. However it is trivial to create your own Type constraint libraries |
d249a614 |
55 | and export them to a controller that wishes to use them. We recommend using L<Type::Tiny> or |
480d94b5 |
56 | L<MooseX::Types> for this. Here is an example using some extended type constraints via |
57 | the 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 | |
73 | This would match URLs like "http://localhost/user/11-11-2015" for example. If you've been |
74 | missing the old RegExp matching, this can emulate a good chunk of that ability, and more. |
75 | |
76 | A tutorial on how to make custom type libraries is outside the scope of this document. I'd |
77 | recommend looking at the copious documentation in L<Type::Tiny> or in L<MooseX::Types> if |
78 | you 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 | |
82 | As previously described, L<Catalyst> will match 'the longest path', which generally means |
83 | that named path / path_parts will take precidence over Args or CaptureArgs. However, what |
84 | will 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 | |
92 | In this case L<Catalyst> will check actions starting from the LAST one defined. Generally |
93 | this means you should put your most specific action rules LAST and your 'catch-alls' first. |
94 | In the above example, since Args(1) will match any argument, you will find that that 'an_int' |
95 | action 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 |
103 | Now requests that match this path would first hit the 'an_int' action and will check to see if |
104 | the argument is an integer. If it is, then the action will execute, otherwise it will pass and |
105 | the dispatcher will check the next matching action (in this case we fall thru to the 'an_any' |
106 | action). |
480d94b5 |
107 | |
108 | =head3 Type Constraints and Chained Actions |
109 | |
110 | Using type constraints in Chained actions works the same as it does for Path and Local or Global |
d249a614 |
111 | actions. The only difference is that you may declare type constraints on CaptureArgs as |
480d94b5 |
112 | well as Args. For Example: |
113 | |
114 | sub chain_base :Chained(/) CaptureArgs(1) { } |
115 | |
116 | sub any_priority_chain :Chained(chain_base) PathPart('') Args(1) { } |
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 | |
124 | sub int_priority_link_any :Chained(link_any) PathPart('') Args(Int) { } |
125 | |
126 | sub link_int :Chained(chain_base) PathPart('') CaptureArgs(Int) { } |
127 | |
128 | sub any_priority_link :Chained(link_int) PathPart('') Args(1) { } |
129 | |
130 | sub int_priority_link :Chained(link_int) PathPart('') Args(Int) { } |
131 | |
132 | These chained actions migth create match tables like the following: |
133 | |
134 | [debug] Loaded Chained actions: |
135 | .----------------------------------------------+----------------------------------------------. |
136 | | Path Spec | Private | |
137 | +----------------------------------------------+----------------------------------------------+ |
138 | | /chain_base/*/* | /chain_base (1) | |
139 | | | => /any_priority_chain | |
140 | | /chain_base/*/*/* | /chain_base (1) | |
141 | | | -> /link_int (1) | |
142 | | | => /any_priority_link | |
143 | | /chain_base/*/*/* | /chain_base (1) | |
144 | | | -> /link_any (1) | |
145 | | | => /any_priority_link_any | |
146 | | /chain_base/*/* | /chain_base (1) | |
147 | | | => /int_priority_chain | |
148 | | /chain_base/*/*/* | /chain_base (1) | |
149 | | | -> /link_int (1) | |
150 | | | => /int_priority_link | |
151 | | /chain_base/*/*/* | /chain_base (1) | |
152 | | | -> /link_any (1) | |
153 | | | => /int_priority_link_any | |
154 | '----------------------------------------------+----------------------------------------------' |
155 | |
156 | As you can see the same general path could be matched by various action chains. In this case |
157 | the rule described in the previous section should be followed, which is that L<Catalyst> |
158 | will start with the last defined action and work upward. For example the action C<int_priority_chain> |
159 | would be checked before C<any_priority_chain>. The same applies for actions that are midway links |
160 | in a longer chain. In this case C<link_int> would be checked before C<link_any>. So as always we |
161 | recommend that you place you priority or most constrainted actions last and you least or catch-all |
162 | actions first. |
163 | |
164 | Although this reverse order checking may seen counter intuitive it does have the added benefit that |
165 | when inheriting controllers any new actions added would take check precedence over those in your |
166 | parent controller or consumed role. |
167 | |
168 | =head1 Conclusion |
169 | |
170 | TBD |
171 | |
172 | =head1 Author |
173 | |
174 | John Napiorkowski L<jjnapiork@cpan.org|email:jjnapiork@cpan.org> |
175 | |
176 | =cut |
177 | |