incorporated static discussion from andyg into Cookbook.pod
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Manual / Cookbook.pod
CommitLineData
fc7ec1d9 1=head1 NAME
2
3Catalyst::Manual::Cookbook - Cooking with Catalyst
4
5=head1 DESCRIPTION
6
aba94964 7Yummy code like your mum used to bake!
fc7ec1d9 8
9=head1 RECIPES
10
11=head2 Force debug screen
12
e6394847 13You can force Catalyst to display the debug screen at the end of the
14request by 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 21If you're tired of removing and adding this all the time, you
e6394847 22can add a condition in the C<end> action:
aff93052 23
e6394847 24 sub end : Private {
25 my ( $self, $c ) = @_;
26 die "forced debug" if $c->req->params->{dump_info};
27 }
28
29Then just add to your query string C<"&dump_info=1">, or the like,
30to force debug output.
aff93052 31
fc7ec1d9 32=head2 Disable statistics
33
34Just add this line to your application class if you don't want those nifty
35statistics in your debug messages.
36
37 sub Catalyst::Log::info { }
38
39=head2 Scaffolding
40
41Scaffolding is very simple with Catalyst.
51ef2818 42Just use Catalyst::Model::CDBI::CRUD as your base class.
fc7ec1d9 43
44 # lib/MyApp/Model/CDBI.pm
45 package MyApp::Model::CDBI;
46
47 use strict;
48 use base 'Catalyst::Model::CDBI::CRUD';
49
50 __PACKAGE__->config(
51 dsn => 'dbi:SQLite:/tmp/myapp.db',
52 relationships => 1
53 );
54
55 1;
56
57 # lib/MyApp.pm
58 package MyApp;
59
60 use Catalyst 'FormValidator';
61
62 __PACKAGE__->config(
63 name => 'My Application',
64 root => '/home/joeuser/myapp/root'
65 );
66
61b1e958 67 sub my_table : Global {
68 my ( $self, $c ) = @_;
69 $c->form( optional => [ MyApp::Model::CDBI::Table->columns ] );
70 $c->forward('MyApp::Model::CDBI::Table');
71 }
fc7ec1d9 72
73 1;
74
e6394847 75Modify the $c->form() parameters to match your needs, and don't forget
76to copy the templates into the template root. Can't find the templates?
77They were in the CRUD model distribution, so you can do B<look
78Catalyst::Model::CDBI::CRUD> from the CPAN shell to find them.
79
80Other Scaffolding modules are in development at the time of writing.
fc7ec1d9 81
5c0ff128 82=head2 Single file upload with Catalyst
aba94964 83
84To implement uploads in Catalyst you need to have a HTML form similiar to
85this:
86
87 <form action="/upload" method="post" enctype="multipart/form-data">
88 <input type="hidden" name="form_submit" value="yes">
89 <input type="file" name="my_file">
90 <input type="submit" value="Send">
91 </form>
92
e6394847 93It's very important not to forget C<enctype="multipart/form-data"> in
94the form.
aba94964 95
96Catalyst Controller module 'upload' action:
97
5c0ff128 98 sub upload : Global {
99 my ($self, $c) = @_;
4d89569d 100
101 if ( $c->request->parameters->{form_submit} eq 'yes' ) {
102
103 if ( my $upload = $c->request->upload('my_file') ) {
47ae6960 104
5c0ff128 105 my $filename = $upload->filename;
47ae6960 106 my $target = "/tmp/upload/$filename";
107
3ffaf022 108 unless ( $upload->link_to($target) || $upload->copy_to($target) ) {
47ae6960 109 die( "Failed to copy '$filename' to '$target': $!" );
5c0ff128 110 }
5c0ff128 111 }
112 }
4d89569d 113
5c0ff128 114 $c->stash->{template} = 'file_upload.html';
115 }
116
117=head2 Multiple file upload with Catalyst
118
e6394847 119Code for uploading multiple files from one form needs a few changes:
5c0ff128 120
e6394847 121The form should have this basic structure:
5c0ff128 122
123 <form action="/upload" method="post" enctype="multipart/form-data">
124 <input type="hidden" name="form_submit" value="yes">
125 <input type="file" name="file1" size="50"><br>
126 <input type="file" name="file2" size="50"><br>
127 <input type="file" name="file3" size="50"><br>
128 <input type="submit" value="Send">
129 </form>
130
e6394847 131And in the Controller:
5c0ff128 132
133 sub upload : Local {
134 my ($self, $c) = @_;
4d89569d 135
136 if ( $c->request->parameters->{form_submit} eq 'yes' ) {
137
138 for my $field ( $c->req->upload ) {
139
02a53b81 140 my $upload = $c->req->upload($field);
4d89569d 141 my $filename = $upload->filename;
47ae6960 142 my $target = "/tmp/upload/$filename";
143
3ffaf022 144 unless ( $upload->link_to($target) || $upload->copy_to($target) ) {
47ae6960 145 die( "Failed to copy '$filename' to '$target': $!" );
aba94964 146 }
147 }
61b1e958 148 }
4d89569d 149
5c0ff128 150 $c->stash->{template} = 'file_upload.html';
151 }
152
e6394847 153C<for my $field ($c-E<gt>req->upload)> loops automatically over all file
154input fields and gets input names. After that is basic file saving code,
155just like in single file upload.
aba94964 156
e6394847 157Notice: C<die>ing might not be what you want to do, when an error
158occurs, but it works as an example. A better idea would be to store
159error C<$!> in $c->stash->{error} and show a custom error template
160displaying this message.
aba94964 161
5c0ff128 162For more information about uploads and usable methods look at
e6394847 163L<Catalyst::Request::Upload> and L<Catalyst::Request>.
aba94964 164
deb90705 165=head2 Authentication with Catalyst::Plugin::Authentication::CDBI
166
167There are (at least) two ways to implement authentication with this plugin:
e6394847 1681) only checking username and password
1692) checking username, password, and the roles the user has
deb90705 170
171For both variants you'll need the following code in your MyApp package:
172
173 use Catalyst qw/Session::FastMmap Static Authentication::CDBI/;
174
175 MyApp->config( authentication => { user_class => 'MyApp::M::MyApp::Users',
176 user_field => 'email',
177 password_field => 'password' });
178
179'user_class' is a Class::DBI class for your users table.
180'user_field' tells which field is used for username lookup (might be
51ef2818 181email, first name, surname etc.).
deb90705 182'password_field' is, well, password field in your table and by default
183password is stored in plain text. Authentication::CDBI looks for 'user'
184and 'password' fields in table, if they're not defined in the config.
185
51ef2818 186In PostgreSQL, the users table might be something like:
deb90705 187
51ef2818 188 CREATE TABLE users (
189 user_id serial,
190 name varchar(100),
191 surname varchar(100),
192 password varchar(100),
193 email varchar(100),
194 primary key(user_id)
195 );
deb90705 196
197We'll discuss the first variant for now:
51ef2818 1981. user:password login/auth without roles
deb90705 199
51ef2818 200To log in a user you might use an action like this:
deb90705 201
7c1078a4 202 sub login : Local {
deb90705 203 my ($self, $c) = @_;
204 if ($c->req->params->{username}) {
205 $c->session_login($c->req->params->{username},
61b1e958 206 $c->req->params->{password} );
deb90705 207 if ($c->req->{user}) {
208 $c->forward('?restricted_area');
209 }
210 }
61b1e958 211 }
deb90705 212
7c1078a4 213This action should not go in your MyApp class...if it does, it will
214conflict with the built-in method of the same name. Instead, put it
215in a Controller class.
216
deb90705 217$c->req->params->{username} and $c->req->params->{password} are html
61b1e958 218form parameters from a login form. If login succeeds, then
219$c->req->{user} contains the username of the authenticated user.
deb90705 220
51ef2818 221If you want to remember the user's login status in between further
222requests, then just use the C<$c-E<gt>session_login> method. Catalyst will
223create a session id and session cookie and automatically append session
224id to all urls. So all you have to do is just check $c->req->{user}
61b1e958 225where needed.
deb90705 226
51ef2818 227To log out a user, just call $c->session_logout.
deb90705 228
51ef2818 229Now let's take a look at the second variant:
2302. user:password login/auth with roles
deb90705 231
51ef2818 232To use roles you need to add the following parameters to MyApp->config in the 'authentication' section:
deb90705 233
234 role_class => 'MyApp::M::MyApp::Roles',
235 user_role_class => 'MyApp::M::MyApp::UserRoles',
236 user_role_user_field => 'user_id',
237 user_role_role_field => 'role_id',
238
239Corresponding tables in PostgreSQL could look like this:
240
51ef2818 241 CREATE TABLE roles (
242 role_id serial,
243 name varchar(100),
244 primary key(role_id)
245 );
246
247 CREATE TABLE user_roles (
248 user_role_id serial,
249 user_id int,
250 role_id int,
251 primary key(user_role_id),
252 foreign key(user_id) references users(user_id),
253 foreign key(role_id) references roles(role_id)
254 );
deb90705 255
61b1e958 256The 'roles' table is a list of role names and the 'user_role' table is
257used for the user -> role lookup.
deb90705 258
51ef2818 259Now if a logged-in user wants to see a location which is allowed only
260for people with an 'admin' role, in your controller you can check it
61b1e958 261with:
deb90705 262
61b1e958 263 sub add : Local {
deb90705 264 my ($self, $c) = @_;
265 if ($c->roles(qw/admin/)) {
266 $c->req->output("Your account has the role 'admin.'");
267 } else {
51ef2818 268 $c->req->output("You're not allowed to be here.");
deb90705 269 }
61b1e958 270 }
deb90705 271
51ef2818 272One thing you might need is to forward non-authenticated users to a login
273form if they try to access restricted areas. If you want to do this
274controller-wide (if you have one controller for your admin section) then it's
275best to add a user check to a '!begin' action:
deb90705 276
61b1e958 277 sub begin : Private {
deb90705 278 my ($self, $c) = @_;
279 unless ($c->req->{user}) {
280 $c->req->action(undef); ## notice this!!
281 $c->forward('?login');
282 }
61b1e958 283 }
deb90705 284
51ef2818 285Pay attention to $c->req->action(undef). This is needed because of the
286way $c->forward works - C<forward> to C<login> gets called, but after that
287Catalyst will still execute the action defined in the URI (e.g. if you
288tried to go to C</add>, then first 'begin' will forward to 'login', but after
289that 'add' will nonetheless be executed). So $c->req->action(undef) undefines any
290actions that were to be called and forwards the user where we want him/her
deb90705 291to be.
292
51ef2818 293And this is all you need to do.
deb90705 294
afb208ae 295=head2 Pass-through login (and other actions)
296
297An easy way of having assorted actions that occur during the processing of
298a request that are orthogonal to its actual purpose - logins, silent
299commands etc. Provide actions for these, but when they're required for
300something else fill e.g. a form variable __login and have a sub begin like so:
301
302sub begin : Private {
303 my ($self, $c) = @_;
304 foreach my $action (qw/login docommand foo bar whatever/) {
305 if ($c->req->params->{"__${action}"}) {
306 $c->forward($action);
307 }
308 }
309}
145074c2 310
311=head2 How to use Catalyst without mod_perl
312
313Catalyst applications give optimum performance when run under mod_perl.
61b1e958 314However sometimes mod_perl is not an option, and running under CGI is
51ef2818 315just too slow. There's also an alternative to mod_perl that gives
dec2a2a9 316reasonable performance named FastCGI.
145074c2 317
318B<Using FastCGI>
319
61b1e958 320To quote from L<http://www.fastcgi.com/>: "FastCGI is a language
321independent, scalable, extension to CGI that provides high performance
322without the limitations of specific server APIs." Web server support
323is provided for Apache in the form of C<mod_fastcgi> and there is Perl
324support in the C<FCGI> module. To convert a CGI Catalyst application
325to FastCGI one needs to initialize an C<FCGI::Request> object and loop
326while the C<Accept> method returns zero. The following code shows how
327it is done - and it also works as a normal, single-shot CGI script.
145074c2 328
329 #!/usr/bin/perl
330 use strict;
331 use FCGI;
332 use MyApp;
333
334 my $request = FCGI::Request();
335 while ($request->Accept() >= 0) {
1c61c726 336 MyApp->run;
145074c2 337 }
338
61b1e958 339Any initialization code should be included outside the request-accept
340loop.
145074c2 341
51ef2818 342There is one little complication, which is that C<MyApp-E<gt>run> outputs a
61b1e958 343complete HTTP response including the status line (e.g.:
344"C<HTTP/1.1 200>").
345FastCGI just wants a set of headers, so the sample code captures the
346output and drops the first line if it is an HTTP status line (note:
347this may change).
348
349The Apache C<mod_fastcgi> module is provided by a number of Linux
350distros and is straightforward to compile for most Unix-like systems.
351The module provides a FastCGI Process Manager, which manages FastCGI
352scripts. You configure your script as a FastCGI script with the
353following Apache configuration directives:
145074c2 354
355 <Location /fcgi-bin>
356 AddHandler fastcgi-script fcgi
357 </Location>
358
359or:
360
361 <Location /fcgi-bin>
362 SetHandler fastcgi-script
363 Action fastcgi-script /path/to/fcgi-bin/fcgi-script
364 </Location>
365
366C<mod_fastcgi> provides a number of options for controlling the FastCGI
367scripts spawned; it also allows scripts to be run to handle the
51ef2818 368authentication, authorization, and access check phases.
145074c2 369
61b1e958 370For more information see the FastCGI documentation, the C<FCGI> module
371and L<http://www.fastcgi.com/>.
145074c2 372
e6394847 373=head2 Serving static content
374
375Serving static content in Catalyst can be somewhat tricky; this recipe
376shows one possible solution. Using this recipe will serve all static
377content through Catalyst when developing with the built-in HTTP::Daemon
378server, and will make it easy to use Apache to serve the content when
379your app goes into production.
380
381Static content is best served from a single directory within your root
382directory. Having many different directories such as C<root/css> and
383C<root/images> requires more code to manage, because you must separately
384identify each static directory--if you decide to add a C<root/js>
385directory, you'll need to change your code to account for it. In
386contrast, keeping all static directories as subdirectories of a main
387C<root/static> directory makes things much easier to manager. Here's an
388example of a typical root directory structure:
389
390 root/
391 root/content.tt
392 root/controller/stuff.tt
393 root/header.tt
394 root/static/
395 root/static/css/main.css
396 root/static/images/logo.jpg
397 root/static/js/code.js
398
399
400All static content lives under C<root/static> with everything else being
401Template Toolkit files. Now you can identify the static content by
402matching C<static> from within Catalyst.
403
404=head3 Serving with HTTP::Daemon (myapp_server.pl)
405
406To serve these files under the standalone server, we first must load the
407Static plugin. Install L<Catalyst::Plugin::Static> if it's not already
408installed.
409
410In your main application class (MyApp.pm), load the plugin:
411
412 use Catalyst qw/-Debug FormValidator Static OtherPlugin/;
413
414You will also need to make sure your end method does I<not> forward
415static content to the view, perhaps like this:
416
417 sub end : Private {
418 my ( $self, $c ) = @_;
419
420 $c->forward( 'MyApp::V::TT' )
421 unless ( $c->res->body || !$c->stash->{template} );
422 }
423
424This code will only forward to the view if a template has been
425previously defined by a controller and if there is not already data in
426C<$c-E<gt>res-E<gt>body>.
427
428Next, create a controller to handle requests for the /static path. Use
429the Helper to save time. This command will create a stub controller as
430C<lib/MyApp/C/Static.pm>.
431
432 $ script/myapp_create.pl controller Static
433
434Edit the file and add the following methods:
435
436 # serve all files under /static as static files
437 sub default : Path('/static') {
438 my ( $self, $c ) = @_;
439
440 # Optional, allow the browser to cache the content
441 $c->res->headers->header( 'Cache-Control' => 'max-age=86400' );
442
443 $c->serve_static; # from Catalyst::Plugin::Static
444 }
445
446 # also handle requests for /favicon.ico
447 sub favicon : Path('/favicon.ico') {
448 my ( $self, $c ) = @_;
449
450 $c->serve_static;
451 }
452
453You can also define a different icon for the browser to use instead of
454favicon.ico by using this in your HTML header:
455
456 <link rel="icon" href="/static/myapp.ico" type="image/x-icon" />
457
458
459=head3 Common problems
460
461The Static plugin makes use of the C<shared-mime-info> package to
462automatically determine MIME types. This package is notoriously
463difficult to install, especially on win32 and OSX. For OSX the easiest
464path might be to install Fink, then use C<apt-get install
465shared-mime-info>. Restart the server, and everything should be fine.
466
467Make sure you are using the latest version (>= 0.16) for best
468results. If you are having errors serving CSS files, or if they get
469served as text/plain instead of text/css, you may have an outdated
470shared-mime-info version. You may also wish to simply use the following
471code in your Static controller:
472
473 if ($c->req->path =~ /css$/i) {
474 $c->serve_static( "text/css" );
475 } else {
476 $c->serve_static;
477 }
478
479=head3 Serving with Apache
480
481When using Apache, you can completely bypass Catalyst and the Static
482controller by intercepting requests for the C<root/static> path at the
483server level. All that is required is to define a DocumentRoot and add a
484separate Location block for your static content. Here is a complete
485config for this application under mod_perl 1.x; variations, some of
486which could be simpler, are left as an exercise for the reader:
487
488 <Perl>
489 use lib qw(/var/www/MyApp/lib);
490 </Perl>
491 PerlModule MyApp
492
493 <VirtualHost *>
494 ServerName myapp.example.com
495 DocumentRoot /var/www/MyApp/root
496 <Location />
497 SetHandler perl-script
498 PerlHandler MyApp
499 </Location>
500 <LocationMatch "/(static|favicon.ico)">
501 SetHandler default-handler
502 </LocationMatch>
503 </VirtualHost>
504
505=head2 Forwarding with arguments
2343e117 506
b284d6a7 507Sometimes you want to pass along arguments when forwarding to another
e6394847 508action. This can be accomplished by simply setting the arguments before
509the forward:
2343e117 510
511 $c->req->args([qw/arg1 arg2 arg3/]);
512 $c->forward('/wherever');
513
fc7ec1d9 514=head1 AUTHOR
515
516Sebastian Riedel, C<sri@oook.de>
e6394847 517Danijel Milicevic, C<me@danijel.de>
518Viljo Marrandi, C<vilts@yahoo.com>
519Marcus Ramberg, C<mramberg@cpan.org>
520Andy Grundman, C<andy@hybridized.org>
fc7ec1d9 521
522=head1 COPYRIGHT
523
61b1e958 524This program is free software, you can redistribute it and/or modify it
525under the same terms as Perl itself.