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 | |
eff5f524 |
13 | You can force Catalyst to display the debug screen at the end of the request by |
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 | |
379ca371 |
21 | If you're tired of removing and adding this all the time, you can add a |
22 | condition in the C<end> action. For example: |
23 | |
24 | sub end : Private { |
25 | my ( $self, $c ) = @_; |
26 | die "forced debug" if $c->req->params->{dump_info}; |
27 | } |
28 | |
29 | Then just add to your query string C<"&dump_info=1">, or the like, to |
30 | force debug output. |
aff93052 |
31 | |
aff93052 |
32 | |
fc7ec1d9 |
33 | =head2 Disable statistics |
34 | |
35 | Just add this line to your application class if you don't want those nifty |
36 | statistics in your debug messages. |
37 | |
38 | sub Catalyst::Log::info { } |
39 | |
40 | =head2 Scaffolding |
41 | |
42 | Scaffolding is very simple with Catalyst. |
fc7ec1d9 |
43 | |
4b8cb778 |
44 | The recommended way is to use Catalyst::Helper::Controller::Scaffold. |
fc7ec1d9 |
45 | |
4b8cb778 |
46 | Just install this module, and to scaffold a Class::DBI Model class, do the following: |
fc7ec1d9 |
47 | |
4b8cb778 |
48 | ./script/myapp_create controller <name> Scaffold <CDBI::Class>Scaffolding |
fc7ec1d9 |
49 | |
fc7ec1d9 |
50 | |
fc7ec1d9 |
51 | |
fc7ec1d9 |
52 | |
822fe954 |
53 | =head2 File uploads |
54 | |
55 | =head3 Single file upload with Catalyst |
aba94964 |
56 | |
26e73131 |
57 | To implement uploads in Catalyst, you need to have a HTML form similar to |
aba94964 |
58 | this: |
59 | |
60 | <form action="/upload" method="post" enctype="multipart/form-data"> |
61 | <input type="hidden" name="form_submit" value="yes"> |
62 | <input type="file" name="my_file"> |
63 | <input type="submit" value="Send"> |
64 | </form> |
65 | |
379ca371 |
66 | It's very important not to forget C<enctype="multipart/form-data"> in |
67 | the form. |
aba94964 |
68 | |
69 | Catalyst Controller module 'upload' action: |
70 | |
5c0ff128 |
71 | sub upload : Global { |
72 | my ($self, $c) = @_; |
4d89569d |
73 | |
74 | if ( $c->request->parameters->{form_submit} eq 'yes' ) { |
75 | |
76 | if ( my $upload = $c->request->upload('my_file') ) { |
b248fa4a |
77 | |
5c0ff128 |
78 | my $filename = $upload->filename; |
47ae6960 |
79 | my $target = "/tmp/upload/$filename"; |
b248fa4a |
80 | |
3ffaf022 |
81 | unless ( $upload->link_to($target) || $upload->copy_to($target) ) { |
47ae6960 |
82 | die( "Failed to copy '$filename' to '$target': $!" ); |
5c0ff128 |
83 | } |
5c0ff128 |
84 | } |
85 | } |
b248fa4a |
86 | |
5c0ff128 |
87 | $c->stash->{template} = 'file_upload.html'; |
88 | } |
89 | |
822fe954 |
90 | =head3 Multiple file upload with Catalyst |
5c0ff128 |
91 | |
379ca371 |
92 | Code for uploading multiple files from one form needs a few changes: |
5c0ff128 |
93 | |
379ca371 |
94 | The form should have this basic structure: |
5c0ff128 |
95 | |
96 | <form action="/upload" method="post" enctype="multipart/form-data"> |
97 | <input type="hidden" name="form_submit" value="yes"> |
98 | <input type="file" name="file1" size="50"><br> |
99 | <input type="file" name="file2" size="50"><br> |
100 | <input type="file" name="file3" size="50"><br> |
101 | <input type="submit" value="Send"> |
102 | </form> |
103 | |
379ca371 |
104 | And in the controller: |
5c0ff128 |
105 | |
106 | sub upload : Local { |
107 | my ($self, $c) = @_; |
4d89569d |
108 | |
109 | if ( $c->request->parameters->{form_submit} eq 'yes' ) { |
110 | |
111 | for my $field ( $c->req->upload ) { |
112 | |
02a53b81 |
113 | my $upload = $c->req->upload($field); |
4d89569d |
114 | my $filename = $upload->filename; |
47ae6960 |
115 | my $target = "/tmp/upload/$filename"; |
b248fa4a |
116 | |
3ffaf022 |
117 | unless ( $upload->link_to($target) || $upload->copy_to($target) ) { |
47ae6960 |
118 | die( "Failed to copy '$filename' to '$target': $!" ); |
aba94964 |
119 | } |
120 | } |
61b1e958 |
121 | } |
4d89569d |
122 | |
5c0ff128 |
123 | $c->stash->{template} = 'file_upload.html'; |
124 | } |
125 | |
379ca371 |
126 | C<for my $field ($c-E<gt>req->upload)> loops automatically over all file |
127 | input fields and gets input names. After that is basic file saving code, |
128 | just like in single file upload. |
aba94964 |
129 | |
379ca371 |
130 | Notice: C<die>ing might not be what you want to do, when an error |
131 | occurs, but it works as an example. A better idea would be to store |
132 | error C<$!> in $c->stash->{error} and show a custom error template |
133 | displaying this message. |
aba94964 |
134 | |
5c0ff128 |
135 | For more information about uploads and usable methods look at |
379ca371 |
136 | L<Catalyst::Request::Upload> and L<Catalyst::Request>. |
aba94964 |
137 | |
deb90705 |
138 | =head2 Authentication with Catalyst::Plugin::Authentication::CDBI |
139 | |
140 | There are (at least) two ways to implement authentication with this plugin: |
eff5f524 |
141 | 1) only checking username and password; |
379ca371 |
142 | 2) checking username, password, and the roles the user has |
deb90705 |
143 | |
144 | For both variants you'll need the following code in your MyApp package: |
145 | |
146 | use Catalyst qw/Session::FastMmap Static Authentication::CDBI/; |
147 | |
148 | MyApp->config( authentication => { user_class => 'MyApp::M::MyApp::Users', |
149 | user_field => 'email', |
150 | password_field => 'password' }); |
151 | |
152 | 'user_class' is a Class::DBI class for your users table. |
153 | 'user_field' tells which field is used for username lookup (might be |
51ef2818 |
154 | email, first name, surname etc.). |
deb90705 |
155 | 'password_field' is, well, password field in your table and by default |
156 | password is stored in plain text. Authentication::CDBI looks for 'user' |
157 | and 'password' fields in table, if they're not defined in the config. |
158 | |
51ef2818 |
159 | In PostgreSQL, the users table might be something like: |
deb90705 |
160 | |
51ef2818 |
161 | CREATE TABLE users ( |
162 | user_id serial, |
163 | name varchar(100), |
164 | surname varchar(100), |
165 | password varchar(100), |
166 | email varchar(100), |
167 | primary key(user_id) |
168 | ); |
deb90705 |
169 | |
170 | We'll discuss the first variant for now: |
51ef2818 |
171 | 1. user:password login/auth without roles |
deb90705 |
172 | |
51ef2818 |
173 | To log in a user you might use an action like this: |
deb90705 |
174 | |
7c1078a4 |
175 | sub login : Local { |
deb90705 |
176 | my ($self, $c) = @_; |
177 | if ($c->req->params->{username}) { |
178 | $c->session_login($c->req->params->{username}, |
61b1e958 |
179 | $c->req->params->{password} ); |
deb90705 |
180 | if ($c->req->{user}) { |
9bee7d37 |
181 | $c->forward('/restricted_area'); |
deb90705 |
182 | } |
183 | } |
61b1e958 |
184 | } |
deb90705 |
185 | |
7c1078a4 |
186 | This action should not go in your MyApp class...if it does, it will |
187 | conflict with the built-in method of the same name. Instead, put it |
188 | in a Controller class. |
189 | |
deb90705 |
190 | $c->req->params->{username} and $c->req->params->{password} are html |
61b1e958 |
191 | form parameters from a login form. If login succeeds, then |
192 | $c->req->{user} contains the username of the authenticated user. |
deb90705 |
193 | |
51ef2818 |
194 | If you want to remember the user's login status in between further |
195 | requests, then just use the C<$c-E<gt>session_login> method. Catalyst will |
196 | create a session id and session cookie and automatically append session |
197 | id to all urls. So all you have to do is just check $c->req->{user} |
61b1e958 |
198 | where needed. |
deb90705 |
199 | |
51ef2818 |
200 | To log out a user, just call $c->session_logout. |
deb90705 |
201 | |
51ef2818 |
202 | Now let's take a look at the second variant: |
203 | 2. user:password login/auth with roles |
deb90705 |
204 | |
51ef2818 |
205 | To use roles you need to add the following parameters to MyApp->config in the 'authentication' section: |
deb90705 |
206 | |
207 | role_class => 'MyApp::M::MyApp::Roles', |
208 | user_role_class => 'MyApp::M::MyApp::UserRoles', |
209 | user_role_user_field => 'user_id', |
210 | user_role_role_field => 'role_id', |
211 | |
212 | Corresponding tables in PostgreSQL could look like this: |
213 | |
51ef2818 |
214 | CREATE TABLE roles ( |
215 | role_id serial, |
216 | name varchar(100), |
217 | primary key(role_id) |
218 | ); |
219 | |
220 | CREATE TABLE user_roles ( |
221 | user_role_id serial, |
222 | user_id int, |
223 | role_id int, |
224 | primary key(user_role_id), |
225 | foreign key(user_id) references users(user_id), |
226 | foreign key(role_id) references roles(role_id) |
227 | ); |
deb90705 |
228 | |
61b1e958 |
229 | The 'roles' table is a list of role names and the 'user_role' table is |
230 | used for the user -> role lookup. |
deb90705 |
231 | |
51ef2818 |
232 | Now if a logged-in user wants to see a location which is allowed only |
233 | for people with an 'admin' role, in your controller you can check it |
61b1e958 |
234 | with: |
deb90705 |
235 | |
61b1e958 |
236 | sub add : Local { |
deb90705 |
237 | my ($self, $c) = @_; |
238 | if ($c->roles(qw/admin/)) { |
016373e6 |
239 | $c->res->output("Your account has the role 'admin.'"); |
deb90705 |
240 | } else { |
016373e6 |
241 | $c->res->output("You're not allowed to be here."); |
deb90705 |
242 | } |
61b1e958 |
243 | } |
deb90705 |
244 | |
51ef2818 |
245 | One thing you might need is to forward non-authenticated users to a login |
246 | form if they try to access restricted areas. If you want to do this |
247 | controller-wide (if you have one controller for your admin section) then it's |
9bee7d37 |
248 | best to add a user check to a 'begin' action: |
deb90705 |
249 | |
61b1e958 |
250 | sub begin : Private { |
deb90705 |
251 | my ($self, $c) = @_; |
252 | unless ($c->req->{user}) { |
253 | $c->req->action(undef); ## notice this!! |
9bee7d37 |
254 | $c->forward('/user/login'); |
deb90705 |
255 | } |
61b1e958 |
256 | } |
deb90705 |
257 | |
26e73131 |
258 | Pay attention to $c->req->action(undef). This is needed because of the |
259 | way $c->forward works - C<forward> to C<login> gets called, but after |
260 | that Catalyst will still execute the action defined in the URI (e.g. if |
261 | you tried to go to C</add>, then first 'begin' will forward to 'login', |
262 | but after that 'add' will nonetheless be executed). So |
263 | $c->req->action(undef) undefines any actions that were to be called and |
264 | forwards the user where we want him/her to be. |
deb90705 |
265 | |
51ef2818 |
266 | And this is all you need to do. |
deb90705 |
267 | |
afb208ae |
268 | =head2 Pass-through login (and other actions) |
269 | |
eff5f524 |
270 | An easy way of having assorted actions that occur during the processing |
271 | of a request that are orthogonal to its actual purpose - logins, silent |
afb208ae |
272 | commands etc. Provide actions for these, but when they're required for |
eff5f524 |
273 | something else fill e.g. a form variable __login and have a sub begin |
274 | like so: |
afb208ae |
275 | |
eff5f524 |
276 | sub begin : Private { |
277 | my ($self, $c) = @_; |
278 | foreach my $action (qw/login docommand foo bar whatever/) { |
279 | if ($c->req->params->{"__${action}"}) { |
280 | $c->forward($action); |
281 | } |
282 | } |
afb208ae |
283 | } |
145074c2 |
284 | |
285 | =head2 How to use Catalyst without mod_perl |
286 | |
287 | Catalyst applications give optimum performance when run under mod_perl. |
61b1e958 |
288 | However sometimes mod_perl is not an option, and running under CGI is |
51ef2818 |
289 | just too slow. There's also an alternative to mod_perl that gives |
dec2a2a9 |
290 | reasonable performance named FastCGI. |
145074c2 |
291 | |
822fe954 |
292 | =head3 Using FastCGI |
145074c2 |
293 | |
61b1e958 |
294 | To quote from L<http://www.fastcgi.com/>: "FastCGI is a language |
295 | independent, scalable, extension to CGI that provides high performance |
296 | without the limitations of specific server APIs." Web server support |
297 | is provided for Apache in the form of C<mod_fastcgi> and there is Perl |
298 | support in the C<FCGI> module. To convert a CGI Catalyst application |
299 | to FastCGI one needs to initialize an C<FCGI::Request> object and loop |
300 | while the C<Accept> method returns zero. The following code shows how |
301 | it is done - and it also works as a normal, single-shot CGI script. |
145074c2 |
302 | |
303 | #!/usr/bin/perl |
304 | use strict; |
305 | use FCGI; |
306 | use MyApp; |
307 | |
308 | my $request = FCGI::Request(); |
309 | while ($request->Accept() >= 0) { |
1c61c726 |
310 | MyApp->run; |
145074c2 |
311 | } |
312 | |
61b1e958 |
313 | Any initialization code should be included outside the request-accept |
314 | loop. |
145074c2 |
315 | |
51ef2818 |
316 | There is one little complication, which is that C<MyApp-E<gt>run> outputs a |
61b1e958 |
317 | complete HTTP response including the status line (e.g.: |
318 | "C<HTTP/1.1 200>"). |
319 | FastCGI just wants a set of headers, so the sample code captures the |
320 | output and drops the first line if it is an HTTP status line (note: |
321 | this may change). |
322 | |
323 | The Apache C<mod_fastcgi> module is provided by a number of Linux |
324 | distros and is straightforward to compile for most Unix-like systems. |
325 | The module provides a FastCGI Process Manager, which manages FastCGI |
326 | scripts. You configure your script as a FastCGI script with the |
327 | following Apache configuration directives: |
145074c2 |
328 | |
329 | <Location /fcgi-bin> |
330 | AddHandler fastcgi-script fcgi |
331 | </Location> |
332 | |
333 | or: |
334 | |
335 | <Location /fcgi-bin> |
336 | SetHandler fastcgi-script |
337 | Action fastcgi-script /path/to/fcgi-bin/fcgi-script |
338 | </Location> |
339 | |
340 | C<mod_fastcgi> provides a number of options for controlling the FastCGI |
341 | scripts spawned; it also allows scripts to be run to handle the |
51ef2818 |
342 | authentication, authorization, and access check phases. |
145074c2 |
343 | |
61b1e958 |
344 | For more information see the FastCGI documentation, the C<FCGI> module |
345 | and L<http://www.fastcgi.com/>. |
b248fa4a |
346 | |
379ca371 |
347 | =head2 Serving static content |
348 | |
349 | Serving static content in Catalyst can be somewhat tricky; this recipe |
350 | shows one possible solution. Using this recipe will serve all static |
351 | content through Catalyst when developing with the built-in HTTP::Daemon |
352 | server, and will make it easy to use Apache to serve the content when |
353 | your app goes into production. |
354 | |
355 | Static content is best served from a single directory within your root |
356 | directory. Having many different directories such as C<root/css> and |
357 | C<root/images> requires more code to manage, because you must separately |
358 | identify each static directory--if you decide to add a C<root/js> |
359 | directory, you'll need to change your code to account for it. In |
360 | contrast, keeping all static directories as subdirectories of a main |
361 | C<root/static> directory makes things much easier to manager. Here's an |
362 | example of a typical root directory structure: |
363 | |
364 | root/ |
365 | root/content.tt |
366 | root/controller/stuff.tt |
367 | root/header.tt |
368 | root/static/ |
369 | root/static/css/main.css |
370 | root/static/images/logo.jpg |
371 | root/static/js/code.js |
372 | |
373 | |
374 | All static content lives under C<root/static> with everything else being |
375 | Template Toolkit files. Now you can identify the static content by |
376 | matching C<static> from within Catalyst. |
377 | |
378 | =head3 Serving with HTTP::Daemon (myapp_server.pl) |
379 | |
380 | To serve these files under the standalone server, we first must load the |
381 | Static plugin. Install L<Catalyst::Plugin::Static> if it's not already |
382 | installed. |
383 | |
384 | In your main application class (MyApp.pm), load the plugin: |
385 | |
386 | use Catalyst qw/-Debug FormValidator Static OtherPlugin/; |
387 | |
388 | You will also need to make sure your end method does I<not> forward |
389 | static content to the view, perhaps like this: |
390 | |
391 | sub end : Private { |
392 | my ( $self, $c ) = @_; |
393 | |
394 | $c->forward( 'MyApp::V::TT' ) |
395 | unless ( $c->res->body || !$c->stash->{template} ); |
396 | } |
397 | |
398 | This code will only forward to the view if a template has been |
399 | previously defined by a controller and if there is not already data in |
400 | C<$c-E<gt>res-E<gt>body>. |
401 | |
402 | Next, create a controller to handle requests for the /static path. Use |
403 | the Helper to save time. This command will create a stub controller as |
404 | C<lib/MyApp/C/Static.pm>. |
405 | |
406 | $ script/myapp_create.pl controller Static |
407 | |
408 | Edit the file and add the following methods: |
409 | |
410 | # serve all files under /static as static files |
411 | sub default : Path('/static') { |
412 | my ( $self, $c ) = @_; |
b248fa4a |
413 | |
379ca371 |
414 | # Optional, allow the browser to cache the content |
415 | $c->res->headers->header( 'Cache-Control' => 'max-age=86400' ); |
416 | |
417 | $c->serve_static; # from Catalyst::Plugin::Static |
418 | } |
419 | |
420 | # also handle requests for /favicon.ico |
421 | sub favicon : Path('/favicon.ico') { |
422 | my ( $self, $c ) = @_; |
b248fa4a |
423 | |
379ca371 |
424 | $c->serve_static; |
425 | } |
426 | |
427 | You can also define a different icon for the browser to use instead of |
428 | favicon.ico by using this in your HTML header: |
429 | |
430 | <link rel="icon" href="/static/myapp.ico" type="image/x-icon" /> |
431 | |
432 | =head3 Common problems |
433 | |
434 | The Static plugin makes use of the C<shared-mime-info> package to |
435 | automatically determine MIME types. This package is notoriously |
26e73131 |
436 | difficult to install, especially on win32 and OS X. For OS X the easiest |
379ca371 |
437 | path might be to install Fink, then use C<apt-get install |
438 | shared-mime-info>. Restart the server, and everything should be fine. |
439 | |
440 | Make sure you are using the latest version (>= 0.16) for best |
441 | results. If you are having errors serving CSS files, or if they get |
442 | served as text/plain instead of text/css, you may have an outdated |
443 | shared-mime-info version. You may also wish to simply use the following |
444 | code in your Static controller: |
445 | |
446 | if ($c->req->path =~ /css$/i) { |
447 | $c->serve_static( "text/css" ); |
448 | } else { |
449 | $c->serve_static; |
450 | } |
451 | |
452 | =head3 Serving with Apache |
453 | |
454 | When using Apache, you can completely bypass Catalyst and the Static |
455 | controller by intercepting requests for the C<root/static> path at the |
456 | server level. All that is required is to define a DocumentRoot and add a |
457 | separate Location block for your static content. Here is a complete |
6d23e1f4 |
458 | config for this application under mod_perl 1.x: |
379ca371 |
459 | |
460 | <Perl> |
461 | use lib qw(/var/www/MyApp/lib); |
462 | </Perl> |
463 | PerlModule MyApp |
b248fa4a |
464 | |
379ca371 |
465 | <VirtualHost *> |
466 | ServerName myapp.example.com |
467 | DocumentRoot /var/www/MyApp/root |
468 | <Location /> |
469 | SetHandler perl-script |
470 | PerlHandler MyApp |
471 | </Location> |
472 | <LocationMatch "/(static|favicon.ico)"> |
473 | SetHandler default-handler |
474 | </LocationMatch> |
475 | </VirtualHost> |
476 | |
6d23e1f4 |
477 | And here's a simpler example that'll get you started: |
478 | |
479 | Alias /static/ "/my/static/files/" |
480 | <Location "/static"> |
481 | SetHandler none |
482 | </Location> |
483 | |
379ca371 |
484 | =head2 Forwarding with arguments |
145074c2 |
485 | |
eff5f524 |
486 | Sometimes you want to pass along arguments when forwarding to another |
487 | action. As of version 5.30, arguments can be passed in the call to |
488 | C<forward>; in earlier versions, you can manually set the arguments in |
489 | the Catalyst Request object: |
e6394847 |
490 | |
eff5f524 |
491 | # version 5.30 and later: |
492 | $c->forward('/wherever', [qw/arg1 arg2 arg3/]); |
2343e117 |
493 | |
eff5f524 |
494 | # pre-5.30 |
2343e117 |
495 | $c->req->args([qw/arg1 arg2 arg3/]); |
496 | $c->forward('/wherever'); |
497 | |
b248fa4a |
498 | (See the L<Catalyst::Manual::Intro> Flow_Control section for more |
499 | information on passing arguments via C<forward>.) |
eff5f524 |
500 | |
822fe954 |
501 | =head2 Configure your application |
502 | |
503 | You configure your application with the C<config> method in your |
504 | application class. This can be hard-coded, or brought in from a |
505 | separate configuration file. |
506 | |
507 | =head3 Using YAML |
508 | |
509 | YAML is a method for creating flexible and readable configuration |
510 | files. It's a great way to keep your Catalyst application configuration |
511 | in one easy-to-understand location. |
512 | |
513 | In your application class (e.g. C<lib/MyApp.pm>): |
514 | |
515 | use YAML; |
516 | # application setup |
517 | __PACKAGE__->config( YAML::LoadFile(__PACKAGE__->config->{'home'} . '/myapp.yml') ); |
518 | __PACKAGE__->setup; |
519 | |
520 | Now create C<myapp.yml> in your application home: |
521 | |
522 | --- #YAML:1.0 |
523 | # DO NOT USE TABS FOR INDENTATION OR label/value SEPARATION!!! |
524 | name: MyApp |
525 | |
526 | # authentication; perldoc Catalyst::Plugin::Authentication::CDBI |
527 | authentication: |
528 | user_class: 'MyApp::M::MyDB::Customer' |
529 | user_field: 'username' |
530 | password_field: 'password' |
531 | password_hash: 'md5' |
532 | role_class: 'MyApp::M::MyDB::Role' |
533 | user_role_class: 'MyApp::M::MyDB::PersonRole' |
534 | user_role_user_field: 'person' |
535 | |
536 | # session; perldoc Catalyst::Plugin::Session::FastMmap |
537 | session: |
538 | expires: '3600' |
539 | rewrite: '0' |
540 | storage: '/tmp/myapp.session' |
541 | |
542 | # emails; perldoc Catalyst::Plugin::Email |
543 | # this passes options as an array :( |
544 | email: |
545 | - SMTP |
546 | - localhost |
547 | |
548 | This is equivalent to: |
549 | |
550 | # configure base package |
551 | __PACKAGE__->config( name => MyApp ); |
552 | # configure authentication |
553 | __PACKAGE__->config->{authentication} = { |
554 | user_class => 'MyApp::M::MyDB::Customer', |
555 | ... |
556 | }; |
557 | # configure sessions |
558 | __PACKAGE__->config->{session} = { |
559 | expires => 3600, |
560 | ... |
561 | }; |
562 | # configure email sending |
563 | __PACKAGE__->config->{email} = [qw/SMTP localhost/]; |
564 | |
565 | See also L<YAML>. |
566 | |
3912ee04 |
567 | =head2 Using existing CDBI (etc.) classes with Catalyst |
568 | |
569 | Many people have existing Model classes that they would like to use with |
570 | Catalyst (or, conversely, they want to write Catalyst models that can be |
571 | used outside of Catalyst, e.g. in a cron job). It's trivial to write a |
572 | simple component in Catalyst that slurps in an outside Model: |
573 | |
574 | package MyApp::M::Catalog; |
575 | use base qw/Catalyst::Base Some::Other::CDBI::Module::Catalog/; |
576 | 1; |
577 | |
578 | and that's it! Now C<Some::Other::CDBI::Module::Catalog> is part of your |
579 | Cat app as C<MyApp::M::Catalog>. |
580 | |
6d23e1f4 |
581 | =head2 Delivering a Custom Error Page |
f25a3283 |
582 | |
6d23e1f4 |
583 | By default, Catalyst will display its own error page whenever it |
584 | encounters an error in your application. When running under C<-Debug> |
585 | mode, the error page is a useful screen including the error message and |
586 | a full Data::Dumper output of the C<$c> context object. When not in |
587 | C<-Debug>, users see a simple "Please come back later" screen. |
f25a3283 |
588 | |
26e73131 |
589 | To use a custom error page, use a special C<end> method to short-circuit |
6d23e1f4 |
590 | the error processing. The following is an example; you might want to |
591 | adjust it further depending on the needs of your application (for |
592 | example, any calls to C<fillform> will probably need to go into this |
593 | C<end> method; see L<Catalyst::Plugin::FillInForm>). |
f25a3283 |
594 | |
6d23e1f4 |
595 | sub end : Private { |
596 | my ( $self, $c ) = @_; |
b248fa4a |
597 | |
6d23e1f4 |
598 | if ( scalar @{ $c->error } ) { |
599 | $c->stash->{errors} = $c->error; |
600 | $c->stash->{template} = 'errors.tt'; |
601 | $c->forward('MyApp::View::TT'); |
121baf93 |
602 | $c->error(0); |
6d23e1f4 |
603 | } |
b248fa4a |
604 | |
6d23e1f4 |
605 | return 1 if $c->response->status =~ /^3\d\d$/; |
606 | return 1 if $c->response->body; |
b248fa4a |
607 | |
6d23e1f4 |
608 | unless ( $c->response->content_type ) { |
609 | $c->response->content_type('text/html; charset=utf-8'); |
610 | } |
b248fa4a |
611 | |
6d23e1f4 |
612 | $c->forward('MyApp::View::TT'); |
613 | } |
f25a3283 |
614 | |
6d23e1f4 |
615 | You can manually set errors in your code to trigger this page by calling |
f25a3283 |
616 | |
6d23e1f4 |
617 | $c->error( 'You broke me!' ); |
f25a3283 |
618 | |
d761e049 |
619 | =head2 Require user logins |
620 | |
621 | It's often useful to restrict access to your application to a set of |
622 | registered users, forcing everyone else to the login page until they're |
623 | signed in. |
624 | |
625 | To implement this in your application make sure you have a customer |
626 | table with username and password fields and a corresponding Model class |
627 | in your Catalyst application, then make the following changes: |
628 | |
629 | =head3 lib/MyApp.pm |
630 | |
158c8782 |
631 | use Catalyst qw/ |
632 | Authentication |
633 | Authentication::Store::DBIC |
634 | Authentication::Credential::Password |
635 | /; |
636 | |
637 | __PACKAGE__->config->{authentication}->{dbic} = { |
638 | 'user_class' => 'My::Model::DBIC::User', |
d761e049 |
639 | 'user_field' => 'username', |
158c8782 |
640 | 'password_field' => 'password' |
641 | 'password_type' => 'hashed', |
642 | 'password_hash_type'=> 'SHA-1' |
d761e049 |
643 | }; |
644 | |
645 | sub auto : Private { |
646 | my ($self, $c) = @_; |
647 | my $login_path = 'user/login'; |
648 | |
649 | # allow people to actually reach the login page! |
158c8782 |
650 | if ($c->request->path eq $login_path) { |
d761e049 |
651 | return 1; |
652 | } |
653 | |
158c8782 |
654 | # if a user doesn't exist, force login |
655 | if ( !$c->user_exists ) { |
d761e049 |
656 | # force the login screen to be shown |
158c8782 |
657 | $c->response->redirect($c->request->base . $login_path); |
d761e049 |
658 | } |
659 | |
158c8782 |
660 | # otherwise, we have a user - continue with the processing chain |
d761e049 |
661 | return 1; |
662 | } |
663 | |
664 | =head3 lib/MyApp/C/User.pm |
665 | |
666 | sub login : Path('/user/login') { |
667 | my ($self, $c) = @_; |
668 | |
669 | # default template |
670 | $c->stash->{'template'} = "user/login.tt"; |
671 | # default form message |
672 | $c->stash->{'message'} = 'Please enter your username and password'; |
673 | |
158c8782 |
674 | if ( $c->request->param('username') ) { |
d761e049 |
675 | # try to log the user in |
158c8782 |
676 | # login() is provided by ::Authentication::Credential::Password |
677 | if( $c->login( |
678 | $c->request->param('username'), |
679 | $c->request->param('password'), |
680 | ); |
b248fa4a |
681 | |
158c8782 |
682 | # if login() returns 1, user is now logged in |
683 | $c->response->redirect('/some/page'); |
d761e049 |
684 | } |
685 | |
686 | # otherwise we failed to login, try again! |
158c8782 |
687 | $c->stash->{'message'} = |
688 | 'Unable to authenticate the login details supplied'; |
d761e049 |
689 | } |
690 | } |
691 | |
692 | sub logout : Path('/user/logout') { |
693 | my ($self, $c) = @_; |
158c8782 |
694 | # log the user out |
695 | $c->logout; |
d761e049 |
696 | |
697 | # do the 'default' action |
158c8782 |
698 | $c->response->redirect($c->request->base); |
699 | } |
d761e049 |
700 | |
701 | |
702 | =head3 root/base/user/login.tt |
703 | |
704 | [% INCLUDE header.tt %] |
705 | <form action="/user/login" method="POST" name="login_form"> |
706 | [% message %]<br /> |
707 | <label for="username">username:</label><br /> |
708 | <input type="text" id="username" name="username" /><br /> |
709 | |
710 | <label for="password">password:</label><br /> |
711 | <input type="password" id="password" name="password" /><br /> |
712 | |
713 | <input type="submit" value="log in" name="form_submit" /> |
714 | </form> |
715 | [% INCLUDE footer.tt %] |
716 | |
158c8782 |
717 | =head2 Role-based Authorization |
718 | |
719 | For more advanced access control, you may want to consider using role-based |
720 | authorization. This means you can assign different roles to each user, e.g. |
721 | "user", "admin", etc. |
722 | |
723 | The C<login> and C<logout> methods and view template are exactly the same as |
724 | in the previous example. |
725 | |
726 | The L<Catalyst::Plugin::Authorization::Roles> plugin is required when |
727 | implementing roles: |
728 | |
729 | use Catalyst qw/ |
730 | Authentication |
731 | Authentication::Credential::Password |
732 | Authentication::Store::Htpasswd |
733 | Authorization::Roles |
734 | /; |
735 | |
736 | Roles are implemented automatically when using |
737 | L<Catalyst::Authetication::Store::Htpasswd>: |
738 | |
739 | # no additional role configuration required |
740 | __PACKAGE__->config->{authentication}{htpasswd} = "passwdfile"; |
741 | |
742 | Or can be set up manually when using L<Catalyst::Authentication::Store::DBIC>: |
743 | |
744 | # Authorization using a many-to-many role relationship |
745 | __PACKAGE__->config->{authorization}{dbic} = { |
746 | 'role_class' => 'My::Model::DBIC::Role', |
747 | 'role_field' => 'name', |
748 | 'user_role_user_field' => 'user', |
749 | |
750 | # DBIx::Class only (omit if using Class::DBI) |
751 | 'role_rel' => 'user_role', |
752 | |
753 | # Class::DBI only, (omit if using DBIx::Class) |
754 | 'user_role_class' => 'My::Model::CDBI::UserRole' |
755 | 'user_role_role_field' => 'role', |
756 | }; |
757 | |
758 | To restrict access to any action, you can use the C<check_user_roles> method: |
759 | |
760 | sub restricted : Local { |
761 | my ( $self, $c ) = @_; |
762 | |
763 | $c->detach("unauthorized") |
764 | unless $c->check_user_roles( "admin" ); |
765 | |
766 | # do something restricted here |
767 | } |
768 | |
769 | You can also use the C<assert_user_roles> method. This just gives an error if |
770 | the current user does not have one of the required roles: |
771 | |
772 | sub also_restricted : Global { |
773 | my ( $self, $c ) = @_; |
774 | $c->assert_user_roles( qw/ user admin / ); |
775 | } |
776 | |
fc7ec1d9 |
777 | =head1 AUTHOR |
778 | |
779 | Sebastian Riedel, C<sri@oook.de> |
379ca371 |
780 | Danijel Milicevic, C<me@danijel.de> |
781 | Viljo Marrandi, C<vilts@yahoo.com> |
822fe954 |
782 | Marcus Ramberg, C<mramberg@cpan.org> |
783 | Jesse Sheidlower, C<jester@panix.com> |
379ca371 |
784 | Andy Grundman, C<andy@hybridized.org> |
3912ee04 |
785 | Chisel Wright, C<pause@herlpacker.co.uk> |
158c8782 |
786 | Will Hawes, C<info@whawes.co.uk> |
fc7ec1d9 |
787 | |
788 | =head1 COPYRIGHT |
789 | |
61b1e958 |
790 | This program is free software, you can redistribute it and/or modify it |
791 | under the same terms as Perl itself. |