X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Manual.git;a=blobdiff_plain;f=lib%2FCatalyst%2FManual%2FCookbook.pod;h=9521beb06c09c05d7488dde125d7bc290cbbf088;hp=2ce3a56f6b548b75af1e86fa636ca9e9425e6e5e;hb=bf6900ba7094f316737b7ed55ec44dea547f39c0;hpb=c718cfb669a068d7cfebc8b155ea1aec7d5cc84e diff --git a/lib/Catalyst/Manual/Cookbook.pod b/lib/Catalyst/Manual/Cookbook.pod index 2ce3a56..9521beb 100644 --- a/lib/Catalyst/Manual/Cookbook.pod +++ b/lib/Catalyst/Manual/Cookbook.pod @@ -11,7 +11,7 @@ Yummy code like your mum used to bake! =head1 Basics These recipes cover some basic stuff that is worth knowing for -catalyst developers. +Catalyst developers. =head2 Delivering a Custom Error Page @@ -33,9 +33,12 @@ to go into this C method; see L). if ( scalar @{ $c->error } ) { $c->stash->{errors} = $c->error; + for my $error ( @{ $c->error } ) { + $c->log->error($error); + } $c->stash->{template} = 'errors.tt'; $c->forward('MyApp::View::TT'); - $c->error(0); + $c->clear_errors; } return 1 if $c->response->status =~ /^3\d\d$/; @@ -62,7 +65,7 @@ nifty statistics in your debug messages. =head2 Enable debug status in the environment Normally you enable the debugging info by adding the C<-Debug> flag to -your C statement. However, you can also enable it using +your C statement . However, you can also enable it using environment variable, so you can (for example) get debug info without modifying your application scripts. Just set C or CMYAPPE_DEBUG> to a true value. @@ -112,19 +115,28 @@ reference. =head3 EXAMPLE - use Catalyst qw/ - Session - Session::Store::FastMmap - Session::State::Cookie - /; + package MyApp; + use Moose; + use namespace::autoclean; + use Catalyst qw/ + Session + Session::Store::FastMmap + Session::State::Cookie + /; + extends 'Catalyst'; + __PACKAGE__->setup; + package MyApp::Controller::Foo; + use Moose; + use namespace::autoclean; + BEGIN { extends 'Catalyst::Controller' }; ## Write data into the session sub add_item : Local { my ( $self, $c ) = @_; - my $item_id = $c->req->param("item"); + my $item_id = $c->req->params->{item}; push @{ $c->session->{items} }, $item_id; @@ -160,36 +172,27 @@ You configure your application with the C method in your application class. This can be hard-coded, or brought in from a separate configuration file. -=head3 Using YAML +=head3 Using Config::General -YAML is a method for creating flexible and readable configuration -files. It's a great way to keep your Catalyst application -configuration in one easy-to-understand location. +L is a method for creating flexible +and readable configuration files. It's a great way to keep your +Catalyst application configuration in one easy-to-understand location. -In your application class (e.g. C): +Now create C in your application home: - use YAML; - # application setup - __PACKAGE__->config( YAML::LoadFile(__PACKAGE__->config->{'home'} . '/myapp.yml') ); - __PACKAGE__->setup; - -Now create C in your application home: - - --- #YAML:1.0 - # DO NOT USE TABS FOR INDENTATION OR label/value SEPARATION!!! - name: MyApp + name MyApp # session; perldoc Catalyst::Plugin::Session::FastMmap - session: - expires: '3600' - rewrite: '0' - storage: '/tmp/myapp.session' + + expires 3600 + rewrite 0 + storage /tmp/myapp.session + # emails; perldoc Catalyst::Plugin::Email # this passes options as an array :( - email: - - SMTP - - localhost + Mail SMTP + Mail localhost This is equivalent to: @@ -208,11 +211,11 @@ This is equivalent to: # configure email sending __PACKAGE__->config->{email} = [qw/SMTP localhost/]; -See also L. +See also L. =head1 Skipping your VCS's directories -Catalyst uses Module::Pluggable to load Models, Views and Controllers. +Catalyst uses Module::Pluggable to load Models, Views, and Controllers. Module::Pluggable will scan through all directories and load modules it finds. Sometimes you might want to skip some of these directories, for example when your version control system makes a subdirectory with @@ -221,7 +224,7 @@ Catalyst skips subversion and CVS directories already, there are other source control systems. Here is the configuration you need to add their directories to the list to skip. -You can make catalyst skip these directories using the Catalyst config: +You can make Catalyst skip these directories using the Catalyst config: # Configure the application __PACKAGE__->config( @@ -234,7 +237,7 @@ and other options. =head1 Users and Access Control -Most multiuser, and some single user web applications require that +Most multiuser, and some single-user web applications require that users identify themselves, and the application is often required to define those roles. The recipes below describe some ways of doing this. @@ -243,7 +246,7 @@ this. This is extensively covered in other documentation; see in particular L and the Authentication chapter -of the Tutorial at L. +of the Tutorial at L. =head2 Pass-through login (and other actions) @@ -262,67 +265,6 @@ like so: } } - -=head2 Role-based Authorization - -For more advanced access control, you may want to consider using role-based -authorization. This means you can assign different roles to each user, e.g. -"user", "admin", etc. - -The C and C methods and view template are exactly the same as -in the previous example. - -The L plugin is required when -implementing roles: - - use Catalyst qw/ - Authentication - Authentication::Credential::Password - Authentication::Store::Htpasswd - Authorization::Roles - /; - -Roles are implemented automatically when using -L: - - # no additional role configuration required - __PACKAGE__->config->{authentication}{htpasswd} = "passwdfile"; - -Or can be set up manually when using L: - - # Authorization using a many-to-many role relationship - __PACKAGE__->config->{authorization}{dbic} = { - 'role_class' => 'My::Model::DBIC::Role', - 'role_field' => 'name', - 'user_role_user_field' => 'user', - - # DBIx::Class only (omit if using Class::DBI) - 'role_rel' => 'user_role', - - # Class::DBI only, (omit if using DBIx::Class) - 'user_role_class' => 'My::Model::CDBI::UserRole' - 'user_role_role_field' => 'role', - }; - -To restrict access to any action, you can use the C method: - - sub restricted : Local { - my ( $self, $c ) = @_; - - $c->detach("unauthorized") - unless $c->check_user_roles( "admin" ); - - # do something restricted here - } - -You can also use the C method. This just gives an -error if the current user does not have one of the required roles: - - sub also_restricted : Global { - my ( $self, $c ) = @_; - $c->assert_user_roles( qw/ user admin / ); - } - =head2 Authentication/Authorization This is done in several steps: @@ -339,7 +281,7 @@ verification>. =item Authorization Making sure the user only accesses functions you want them to -access. This is done by checking the verified users data against your +access. This is done by checking the verified user's data against your internal list of groups, or allowed persons for the current page. =back @@ -366,17 +308,17 @@ Examples: A Storage backend contains the actual data representing the users. It is queried by the credential verifiers. Updating the store is not done -within this system, you will need to do it yourself. +within this system; you will need to do it yourself. Examples: - DBIC - Storage using a database. + DBIC - Storage using a database via DBIx::Class. Minimal - Storage using a simple hash (for testing). =head3 User objects A User object is created by either the storage backend or the -credential verifier, and filled with the retrieved user information. +credential verifier, and is filled with the retrieved user information. Examples: @@ -385,7 +327,7 @@ Examples: =head3 ACL authorization ACL stands for Access Control List. The ACL plugin allows you to -regulate access on a path by path basis, by listing which users, or +regulate access on a path-by-path basis, by listing which users, or roles, have access to which paths. =head3 Roles authorization @@ -396,33 +338,62 @@ then be assigned to ACLs, or just checked when needed. =head3 Logging in When you have chosen your modules, all you need to do is call the C<< -$c->login >> method. If called with no parameters, it will try to find +$c->authenticate >> method. If called with no parameters, it will try to find suitable parameters, such as B and B, or you can pass it these values. =head3 Checking roles -Role checking is done by using the C<< $c->check_user_roles >> method, -this will check using the currently logged in user (via C<< $c->user +Role checking is done by using the C<< $c->check_user_roles >> method. +This will check using the currently logged-in user (via C<< $c->user >>). You pass it the name of a role to check, and it returns true if the user is a member. =head3 EXAMPLE - use Catalyst qw/Authentication - Authentication::Credential::Password - Authentication::Store::Htpasswd - Authorization::Roles/; + package MyApp; + use Moose; + use namespace::autoclean; + extends qw/Catalyst/; + use Catalyst qw/ + Authentication + Authorization::Roles + /; + + __PACKAGE__->config( + authentication => { + default_realm => 'test', + realms => { + test => { + credential => { + class => 'Password', + password_field => 'password', + password_type => 'self_check', + }, + store => { + class => 'Htpasswd', + file => 'htpasswd', + }, + }, + }, + }, + ); + + package MyApp::Controller::Root; + use Moose; + use namespace::autoclean; - __PACKAGE__->config->{authentication}{htpasswd} = "passwdfile"; + BEGIN { extends 'Catalyst::Controller' } + + __PACKAGE__->config(namespace => ''); sub login : Local { my ($self, $c) = @_; - if ( my $user = $c->req->param("user") - and my $password = $c->req->param("password") ) + if ( my $user = $c->req->params->{user} + and my $password = $c->req->param->{password} ) { - if ( $c->login( $user, $password ) ) { + if ( $c->authenticate( username => $user, password => $password ) ) { $c->res->body( "hello " . $c->user->name ); } else { # login incorrect @@ -444,11 +415,11 @@ the user is a member. =head3 Using authentication in a testing environment -Ideally, to write tests for authentication/authorization code one -would first set up a test database with known data, then use +Ideally, to write tests for authentication/authorization code one would +first set up a test database with known data, then use L to simulate a user logging -in. Unfortunately the former can be rather awkward, which is why it's -a good thing that the authentication framework is so flexible. +in. Unfortunately this can be rather awkward, which is why it's a good +thing that the authentication framework is so flexible. Instead of using a test database, one can simply change the authentication store to something a bit easier to deal with in a @@ -456,32 +427,23 @@ testing environment. Additionally, this has the advantage of not modifying one's database, which can be problematic if one forgets to use the testing instead of production database. -e.g., - - use Catalyst::Plugin::Authentication::Store::Minimal::Backend; - - # Sets up the user `test_user' with password `test_pass' - MyApp->default_auth_store( - Catalyst::Plugin::Authentication::Store::Minimal::Backend->new({ - test_user => { password => 'test_pass' }, - }) - ); - -Now, your test code can call C<$c->login('test_user', 'test_pass')> and -successfully login, without messing with the database at all. +Alternatively, if you want to authenticate real users, but not have to +worry about their passwords, you can use +L to force all users to +authenticate with a global password. =head3 More information -L has a longer explanation. +L has a longer explanation. =head2 Authorization =head3 Introduction Authorization is the step that comes after -authentication. Authentication establishes that the user agent is -really representing the user we think it's representing, and then -authorization determines what this user is allowed to do. +authentication. Authentication establishes that the user agent is really +representing the user we think it's representing, and then authorization +determines what this user is allowed to do. =head3 Role Based Access Control @@ -495,20 +457,21 @@ pretty nasti!). For example: sub feed_moose : Local { my ( $self, $c ) = @_; - $c->model( "Moose" )->eat( $c->req->param("food") ); + $c->model( "Moose" )->eat( $c->req->params->{food} ); } With this action, anyone can just come into the moose cage and feed the moose, which is a very dangerous thing. We need to restrict this action, so that only a qualified moose feeder can perform that action. -The Authorization::Roles plugin let's us perform role based access +The Authorization::Roles plugin lets us perform role based access control checks. Let's load it: + use parent qw/Catalyst/; use Catalyst qw/ - Authentication # yadda yadda - Authorization::Roles - /; + Authentication + Authorization::Roles + /; And now our action should look like this: @@ -516,7 +479,7 @@ And now our action should look like this: my ( $self, $c ) = @_; if ( $c->check_roles( "moose_feeder" ) ) { - $c->model( "Moose" )->eat( $c->req->param("food") ); + $c->model( "Moose" )->eat( $c->req->params->{food} ); } else { $c->stash->{error} = "unauthorized"; } @@ -549,7 +512,7 @@ administration). Checking for roles all the time can be tedious and error prone. -The Authorization::ACL plugin let's us declare where we'd like checks +The Authorization::ACL plugin lets us declare where we'd like checks to be done automatically for us. For example, we may want to completely block out anyone who isn't a @@ -600,7 +563,7 @@ If this action does not exist, an error will be thrown, which you can clean up in your C private action instead. Also, it's important to note that if you restrict access to "/" then -C, C, etc will also be restricted. +C, C, etc. will also be restricted. MyApp->acl_allow_root_internals; @@ -621,11 +584,14 @@ can be used outside of Catalyst, e.g. in a cron job). It's trivial to write a simple component in Catalyst that slurps in an outside Model: package MyApp::Model::DB; + use base qw/Catalyst::Model::DBIC::Schema/; + __PACKAGE__->config( schema_class => 'Some::DBIC::Schema', connect_info => ['dbi:SQLite:foo.db', '', '', {AutoCommit=>1}]; ); + 1; and that's it! Now C is part of your @@ -635,9 +601,41 @@ Cat app as C. See L. +=head2 Create accessors to preload static data once per server instance + +When you have data that you want to load just once from the model at +startup, instead of for each request, use mk_group_accessors to +create accessors and tie them to resultsets in your package that +inherits from DBIx::Class::Schema: + + package My::Schema; + use base qw/DBIx::Class::Schema/; + __PACKAGE__->register_class('RESULTSOURCEMONIKER', + 'My::Schema::RESULTSOURCE'); + __PACKAGE__->mk_group_accessors('simple' => + qw(ACCESSORNAME1 ACCESSORNAME2 ACCESSORNAMEn)); + + sub connection { + my ($self, @rest) = @_; + $self->next::method(@rest); + # $self is now a live My::Schema object, complete with DB connection + + $self->ACCESSORNAME1([ $self->resultset('RESULTSOURCEMONIKER')->all ]); + $self->ACCESSORNAME2([ $self->resultset('RESULTSOURCEMONIKER')->search({ COLUMN => { '<' => '30' } })->all ]); + $self->ACCESSORNAMEn([ $self->resultset('RESULTSOURCEMONIKER')->find(1) ]); + } + + 1; + +and now in the controller, you can now access any of these without a +per-request fetch: + + $c->stash->{something} = $c->model('My::Schema')->schema->ACCESSORNAME; + + =head2 XMLRPC -Unlike SOAP, XMLRPC is a very simple (and imo elegant) web-services +Unlike SOAP, XMLRPC is a very simple (and elegant) web-services protocol, exchanging small XML messages like these: Request: @@ -699,7 +697,7 @@ later) and SOAP::Lite (for XMLRPCsh.pl). 5. Add a XMLRPC redispatch method and an add method with Remote attribute to lib/MyApp/Controller/API.pm - sub default : Private { + sub default :Path { my ( $self, $c ) = @_; $c->xmlrpc; } @@ -736,13 +734,11 @@ enforce a specific one. my ( $self, $c, $a, $b ) = @_; return RPC::XML::int->new( $a + $b ); } - - =head1 Views Views pertain to the display of your application. As with models, -catalyst is uncommonly flexible. The recipes below are just a start. +Catalyst is uncommonly flexible. The recipes below are just a start. =head2 Catalyst::View::TT @@ -752,8 +748,8 @@ display your data; you can choose to generate HTML, PDF files, or plain text if you wanted. Most Catalyst applications use a template system to generate their HTML, -and though there are several template systems available, Template -Toolkit is probably the most popular. +and though there are several template systems available, +L