Commit | Line | Data |
fc7ec1d9 |
1 | =head1 NAME |
2 | |
3 | Catalyst::Manual::Cookbook - Cooking with Catalyst |
4 | |
5 | =head1 DESCRIPTION |
6 | |
aba94964 |
7 | Yummy code like your mum used to bake! |
fc7ec1d9 |
8 | |
9 | =head1 RECIPES |
10 | |
11 | =head2 Force debug screen |
12 | |
13 | You can force Catalyst to display the debug screen at the end of the request by |
51ef2818 |
14 | placing a C<die()> call in the C<end> action. |
fc7ec1d9 |
15 | |
61b1e958 |
16 | sub end : Private { |
17 | my ( $self, $c ) = @_; |
2343e117 |
18 | die "forced debug"; |
61b1e958 |
19 | } |
fc7ec1d9 |
20 | |
aff93052 |
21 | If you're tired of removing and adding this all the time, you |
51ef2818 |
22 | can easily add a condition. For example: |
aff93052 |
23 | |
2343e117 |
24 | die "force debug" if $c->req->params->{dump_info}; |
aff93052 |
25 | |
fc7ec1d9 |
26 | =head2 Disable statistics |
27 | |
28 | Just add this line to your application class if you don't want those nifty |
29 | statistics in your debug messages. |
30 | |
31 | sub Catalyst::Log::info { } |
32 | |
33 | =head2 Scaffolding |
34 | |
35 | Scaffolding is very simple with Catalyst. |
51ef2818 |
36 | Just use Catalyst::Model::CDBI::CRUD as your base class. |
fc7ec1d9 |
37 | |
38 | # lib/MyApp/Model/CDBI.pm |
39 | package MyApp::Model::CDBI; |
40 | |
41 | use strict; |
42 | use base 'Catalyst::Model::CDBI::CRUD'; |
43 | |
44 | __PACKAGE__->config( |
45 | dsn => 'dbi:SQLite:/tmp/myapp.db', |
46 | relationships => 1 |
47 | ); |
48 | |
49 | 1; |
50 | |
51 | # lib/MyApp.pm |
52 | package MyApp; |
53 | |
54 | use Catalyst 'FormValidator'; |
55 | |
56 | __PACKAGE__->config( |
57 | name => 'My Application', |
58 | root => '/home/joeuser/myapp/root' |
59 | ); |
60 | |
61b1e958 |
61 | sub my_table : Global { |
62 | my ( $self, $c ) = @_; |
63 | $c->form( optional => [ MyApp::Model::CDBI::Table->columns ] ); |
64 | $c->forward('MyApp::Model::CDBI::Table'); |
65 | } |
fc7ec1d9 |
66 | |
67 | 1; |
68 | |
69 | Modify the $c->form() parameters to match your needs, and don't forget to copy |
b114f908 |
70 | the templates into the template root. Can't find the templates? They were in the |
71 | CRUD model distribution, so you can do B<look Catalyst::Model::CDBI::CRUD> from |
72 | the CPAN shell to find them. |
fc7ec1d9 |
73 | |
5c0ff128 |
74 | =head2 Single file upload with Catalyst |
aba94964 |
75 | |
76 | To implement uploads in Catalyst you need to have a HTML form similiar to |
77 | this: |
78 | |
79 | <form action="/upload" method="post" enctype="multipart/form-data"> |
80 | <input type="hidden" name="form_submit" value="yes"> |
81 | <input type="file" name="my_file"> |
82 | <input type="submit" value="Send"> |
83 | </form> |
84 | |
51ef2818 |
85 | It's very important not to forget C<enctype="multipart/form-data"> in form. Uploads will not work without this. |
aba94964 |
86 | |
87 | Catalyst Controller module 'upload' action: |
88 | |
5c0ff128 |
89 | sub upload : Global { |
90 | my ($self, $c) = @_; |
4d89569d |
91 | |
92 | if ( $c->request->parameters->{form_submit} eq 'yes' ) { |
93 | |
94 | if ( my $upload = $c->request->upload('my_file') ) { |
47ae6960 |
95 | |
5c0ff128 |
96 | my $filename = $upload->filename; |
47ae6960 |
97 | my $target = "/tmp/upload/$filename"; |
98 | |
3ffaf022 |
99 | unless ( $upload->link_to($target) || $upload->copy_to($target) ) { |
47ae6960 |
100 | die( "Failed to copy '$filename' to '$target': $!" ); |
5c0ff128 |
101 | } |
5c0ff128 |
102 | } |
103 | } |
4d89569d |
104 | |
5c0ff128 |
105 | $c->stash->{template} = 'file_upload.html'; |
106 | } |
107 | |
108 | =head2 Multiple file upload with Catalyst |
109 | |
110 | Code for uploading multiple files from one form needs little changes compared |
111 | to single file upload. |
112 | |
113 | Form goes like this: |
114 | |
115 | <form action="/upload" method="post" enctype="multipart/form-data"> |
116 | <input type="hidden" name="form_submit" value="yes"> |
117 | <input type="file" name="file1" size="50"><br> |
118 | <input type="file" name="file2" size="50"><br> |
119 | <input type="file" name="file3" size="50"><br> |
120 | <input type="submit" value="Send"> |
121 | </form> |
122 | |
123 | Controller: |
124 | |
125 | sub upload : Local { |
126 | my ($self, $c) = @_; |
4d89569d |
127 | |
128 | if ( $c->request->parameters->{form_submit} eq 'yes' ) { |
129 | |
130 | for my $field ( $c->req->upload ) { |
131 | |
02a53b81 |
132 | my $upload = $c->req->upload($field); |
4d89569d |
133 | my $filename = $upload->filename; |
47ae6960 |
134 | my $target = "/tmp/upload/$filename"; |
135 | |
3ffaf022 |
136 | unless ( $upload->link_to($target) || $upload->copy_to($target) ) { |
47ae6960 |
137 | die( "Failed to copy '$filename' to '$target': $!" ); |
aba94964 |
138 | } |
139 | } |
61b1e958 |
140 | } |
4d89569d |
141 | |
5c0ff128 |
142 | $c->stash->{template} = 'file_upload.html'; |
143 | } |
144 | |
51ef2818 |
145 | C<for my $field ($c-E<gt>req->upload)> loops automatically over all file input |
5c0ff128 |
146 | fields and gets input names. After that is basic file saving code, just like in |
147 | single file upload. |
aba94964 |
148 | |
51ef2818 |
149 | Notice: C<die>ing might not be what you want to do, when an error occurs, but |
150 | it works as an example. A better idea would be to store error C<$!> in |
151 | $c->stash->{error} and show a custom error template displaying this message. |
aba94964 |
152 | |
5c0ff128 |
153 | For more information about uploads and usable methods look at |
154 | C<Catalyst::Request::Upload> and C<Catalyst::Request>. |
aba94964 |
155 | |
deb90705 |
156 | =head2 Authentication with Catalyst::Plugin::Authentication::CDBI |
157 | |
158 | There are (at least) two ways to implement authentication with this plugin: |
51ef2818 |
159 | 1) only checking username and password; |
deb90705 |
160 | 2) checking username, password and the roles the user has |
161 | |
162 | For both variants you'll need the following code in your MyApp package: |
163 | |
164 | use Catalyst qw/Session::FastMmap Static Authentication::CDBI/; |
165 | |
166 | MyApp->config( authentication => { user_class => 'MyApp::M::MyApp::Users', |
167 | user_field => 'email', |
168 | password_field => 'password' }); |
169 | |
170 | 'user_class' is a Class::DBI class for your users table. |
171 | 'user_field' tells which field is used for username lookup (might be |
51ef2818 |
172 | email, first name, surname etc.). |
deb90705 |
173 | 'password_field' is, well, password field in your table and by default |
174 | password is stored in plain text. Authentication::CDBI looks for 'user' |
175 | and 'password' fields in table, if they're not defined in the config. |
176 | |
51ef2818 |
177 | In PostgreSQL, the users table might be something like: |
deb90705 |
178 | |
51ef2818 |
179 | CREATE TABLE users ( |
180 | user_id serial, |
181 | name varchar(100), |
182 | surname varchar(100), |
183 | password varchar(100), |
184 | email varchar(100), |
185 | primary key(user_id) |
186 | ); |
deb90705 |
187 | |
188 | We'll discuss the first variant for now: |
51ef2818 |
189 | 1. user:password login/auth without roles |
deb90705 |
190 | |
51ef2818 |
191 | To log in a user you might use an action like this: |
deb90705 |
192 | |
7c1078a4 |
193 | sub login : Local { |
deb90705 |
194 | my ($self, $c) = @_; |
195 | if ($c->req->params->{username}) { |
196 | $c->session_login($c->req->params->{username}, |
61b1e958 |
197 | $c->req->params->{password} ); |
deb90705 |
198 | if ($c->req->{user}) { |
199 | $c->forward('?restricted_area'); |
200 | } |
201 | } |
61b1e958 |
202 | } |
deb90705 |
203 | |
7c1078a4 |
204 | This action should not go in your MyApp class...if it does, it will |
205 | conflict with the built-in method of the same name. Instead, put it |
206 | in a Controller class. |
207 | |
deb90705 |
208 | $c->req->params->{username} and $c->req->params->{password} are html |
61b1e958 |
209 | form parameters from a login form. If login succeeds, then |
210 | $c->req->{user} contains the username of the authenticated user. |
deb90705 |
211 | |
51ef2818 |
212 | If you want to remember the user's login status in between further |
213 | requests, then just use the C<$c-E<gt>session_login> method. Catalyst will |
214 | create a session id and session cookie and automatically append session |
215 | id to all urls. So all you have to do is just check $c->req->{user} |
61b1e958 |
216 | where needed. |
deb90705 |
217 | |
51ef2818 |
218 | To log out a user, just call $c->session_logout. |
deb90705 |
219 | |
51ef2818 |
220 | Now let's take a look at the second variant: |
221 | 2. user:password login/auth with roles |
deb90705 |
222 | |
51ef2818 |
223 | To use roles you need to add the following parameters to MyApp->config in the 'authentication' section: |
deb90705 |
224 | |
225 | role_class => 'MyApp::M::MyApp::Roles', |
226 | user_role_class => 'MyApp::M::MyApp::UserRoles', |
227 | user_role_user_field => 'user_id', |
228 | user_role_role_field => 'role_id', |
229 | |
230 | Corresponding tables in PostgreSQL could look like this: |
231 | |
51ef2818 |
232 | CREATE TABLE roles ( |
233 | role_id serial, |
234 | name varchar(100), |
235 | primary key(role_id) |
236 | ); |
237 | |
238 | CREATE TABLE user_roles ( |
239 | user_role_id serial, |
240 | user_id int, |
241 | role_id int, |
242 | primary key(user_role_id), |
243 | foreign key(user_id) references users(user_id), |
244 | foreign key(role_id) references roles(role_id) |
245 | ); |
deb90705 |
246 | |
61b1e958 |
247 | The 'roles' table is a list of role names and the 'user_role' table is |
248 | used for the user -> role lookup. |
deb90705 |
249 | |
51ef2818 |
250 | Now if a logged-in user wants to see a location which is allowed only |
251 | for people with an 'admin' role, in your controller you can check it |
61b1e958 |
252 | with: |
deb90705 |
253 | |
61b1e958 |
254 | sub add : Local { |
deb90705 |
255 | my ($self, $c) = @_; |
256 | if ($c->roles(qw/admin/)) { |
257 | $c->req->output("Your account has the role 'admin.'"); |
258 | } else { |
51ef2818 |
259 | $c->req->output("You're not allowed to be here."); |
deb90705 |
260 | } |
61b1e958 |
261 | } |
deb90705 |
262 | |
51ef2818 |
263 | One thing you might need is to forward non-authenticated users to a login |
264 | form if they try to access restricted areas. If you want to do this |
265 | controller-wide (if you have one controller for your admin section) then it's |
266 | best to add a user check to a '!begin' action: |
deb90705 |
267 | |
61b1e958 |
268 | sub begin : Private { |
deb90705 |
269 | my ($self, $c) = @_; |
270 | unless ($c->req->{user}) { |
271 | $c->req->action(undef); ## notice this!! |
272 | $c->forward('?login'); |
273 | } |
61b1e958 |
274 | } |
deb90705 |
275 | |
51ef2818 |
276 | Pay attention to $c->req->action(undef). This is needed because of the |
277 | way $c->forward works - C<forward> to C<login> gets called, but after that |
278 | Catalyst will still execute the action defined in the URI (e.g. if you |
279 | tried to go to C</add>, then first 'begin' will forward to 'login', but after |
280 | that 'add' will nonetheless be executed). So $c->req->action(undef) undefines any |
281 | actions that were to be called and forwards the user where we want him/her |
deb90705 |
282 | to be. |
283 | |
51ef2818 |
284 | And this is all you need to do. |
deb90705 |
285 | |
afb208ae |
286 | =head2 Pass-through login (and other actions) |
287 | |
288 | An easy way of having assorted actions that occur during the processing of |
289 | a request that are orthogonal to its actual purpose - logins, silent |
290 | commands etc. Provide actions for these, but when they're required for |
291 | something else fill e.g. a form variable __login and have a sub begin like so: |
292 | |
293 | sub begin : Private { |
294 | my ($self, $c) = @_; |
295 | foreach my $action (qw/login docommand foo bar whatever/) { |
296 | if ($c->req->params->{"__${action}"}) { |
297 | $c->forward($action); |
298 | } |
299 | } |
300 | } |
145074c2 |
301 | |
302 | =head2 How to use Catalyst without mod_perl |
303 | |
304 | Catalyst applications give optimum performance when run under mod_perl. |
61b1e958 |
305 | However sometimes mod_perl is not an option, and running under CGI is |
51ef2818 |
306 | just too slow. There's also an alternative to mod_perl that gives |
dec2a2a9 |
307 | reasonable performance named FastCGI. |
145074c2 |
308 | |
309 | B<Using FastCGI> |
310 | |
61b1e958 |
311 | To quote from L<http://www.fastcgi.com/>: "FastCGI is a language |
312 | independent, scalable, extension to CGI that provides high performance |
313 | without the limitations of specific server APIs." Web server support |
314 | is provided for Apache in the form of C<mod_fastcgi> and there is Perl |
315 | support in the C<FCGI> module. To convert a CGI Catalyst application |
316 | to FastCGI one needs to initialize an C<FCGI::Request> object and loop |
317 | while the C<Accept> method returns zero. The following code shows how |
318 | it is done - and it also works as a normal, single-shot CGI script. |
145074c2 |
319 | |
320 | #!/usr/bin/perl |
321 | use strict; |
322 | use FCGI; |
323 | use MyApp; |
324 | |
325 | my $request = FCGI::Request(); |
326 | while ($request->Accept() >= 0) { |
1c61c726 |
327 | MyApp->run; |
145074c2 |
328 | } |
329 | |
61b1e958 |
330 | Any initialization code should be included outside the request-accept |
331 | loop. |
145074c2 |
332 | |
51ef2818 |
333 | There is one little complication, which is that C<MyApp-E<gt>run> outputs a |
61b1e958 |
334 | complete HTTP response including the status line (e.g.: |
335 | "C<HTTP/1.1 200>"). |
336 | FastCGI just wants a set of headers, so the sample code captures the |
337 | output and drops the first line if it is an HTTP status line (note: |
338 | this may change). |
339 | |
340 | The Apache C<mod_fastcgi> module is provided by a number of Linux |
341 | distros and is straightforward to compile for most Unix-like systems. |
342 | The module provides a FastCGI Process Manager, which manages FastCGI |
343 | scripts. You configure your script as a FastCGI script with the |
344 | following Apache configuration directives: |
145074c2 |
345 | |
346 | <Location /fcgi-bin> |
347 | AddHandler fastcgi-script fcgi |
348 | </Location> |
349 | |
350 | or: |
351 | |
352 | <Location /fcgi-bin> |
353 | SetHandler fastcgi-script |
354 | Action fastcgi-script /path/to/fcgi-bin/fcgi-script |
355 | </Location> |
356 | |
357 | C<mod_fastcgi> provides a number of options for controlling the FastCGI |
358 | scripts spawned; it also allows scripts to be run to handle the |
51ef2818 |
359 | authentication, authorization, and access check phases. |
145074c2 |
360 | |
61b1e958 |
361 | For more information see the FastCGI documentation, the C<FCGI> module |
362 | and L<http://www.fastcgi.com/>. |
145074c2 |
363 | |
2343e117 |
364 | =head1 Forwarding with a parameter |
365 | |
b284d6a7 |
366 | Sometimes you want to pass along arguments when forwarding to another |
2343e117 |
367 | action. This can easily be accomplished like this: |
368 | |
369 | $c->req->args([qw/arg1 arg2 arg3/]); |
370 | $c->forward('/wherever'); |
371 | |
fc7ec1d9 |
372 | =head1 AUTHOR |
373 | |
374 | Sebastian Riedel, C<sri@oook.de> |
deb90705 |
375 | Danijel Milicevic C<me@danijel.de> |
376 | Viljo Marrandi C<vilts@yahoo.com> |
61b1e958 |
377 | Marcus Ramberg C<mramberg@cpan.org> |
fc7ec1d9 |
378 | |
379 | =head1 COPYRIGHT |
380 | |
61b1e958 |
381 | This program is free software, you can redistribute it and/or modify it |
382 | under the same terms as Perl itself. |