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=8b450c57f184806c0688ad84a4911c6b20db3ff4;hp=f4b756b9cf8e77aaaeb47181a0bc686ae4ec1cf7;hb=e2728084ad684d622fbc47f14097b28cff1f2021;hpb=3cca83594d8071ecc4bc3bd7307f5144a8de9aca diff --git a/lib/Catalyst/Manual/Cookbook.pod b/lib/Catalyst/Manual/Cookbook.pod index f4b756b..8b450c5 100644 --- a/lib/Catalyst/Manual/Cookbook.pod +++ b/lib/Catalyst/Manual/Cookbook.pod @@ -10,21 +10,23 @@ Yummy code like your mum used to bake! =head1 Basics -These recipes cover some basic stuff that is worth knowing for catalyst developers. +These recipes cover some basic stuff that is worth knowing for +Catalyst developers. =head2 Delivering a Custom Error Page By default, Catalyst will display its own error page whenever it encounters an error in your application. When running under C<-Debug> -mode, the error page is a useful screen including the error message and -L output of the relevant parts of the C<$c> context object. -When not in C<-Debug>, users see a simple "Please come back later" screen. +mode, the error page is a useful screen including the error message +and L output of the relevant parts of the C<$c> context +object. When not in C<-Debug>, users see a simple "Please come back +later" screen. -To use a custom error page, use a special C method to short-circuit -the error processing. The following is an example; you might want to -adjust it further depending on the needs of your application (for -example, any calls to C will probably need to go into this -C method; see L). +To use a custom error page, use a special C method to +short-circuit the error processing. The following is an example; you +might want to adjust it further depending on the needs of your +application (for example, any calls to C will probably need +to go into this C method; see L). sub end : Private { my ( $self, $c ) = @_; @@ -52,47 +54,49 @@ You can manually set errors in your code to trigger this page by calling =head2 Disable statistics -Just add this line to your application class if you don't want those nifty -statistics in your debug messages. +Just add this line to your application class if you don't want those +nifty statistics in your debug messages. sub Catalyst::Log::info { } =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. =head2 Sessions -When you have your users identified, you will want to somehow remember that -fact, to save them from having to identify themselves for every single -page. One way to do this is to send the username and password parameters in -every single page, but that's ugly, and won't work for static pages. +When you have your users identified, you will want to somehow remember +that fact, to save them from having to identify themselves for every +single page. One way to do this is to send the username and password +parameters in every single page, but that's ugly, and won't work for +static pages. -Sessions are a method of saving data related to some transaction, and giving -the whole collection a single ID. This ID is then given to the user to return -to us on every page they visit while logged in. The usual way to do this is -using a browser cookie. +Sessions are a method of saving data related to some transaction, and +giving the whole collection a single ID. This ID is then given to the +user to return to us on every page they visit while logged in. The +usual way to do this is using a browser cookie. Catalyst uses two types of plugins to represent sessions: =head3 State -A State module is used to keep track of the state of the session between the -users browser, and your application. +A State module is used to keep track of the state of the session +between the users browser, and your application. -A common example is the Cookie state module, which sends the browser a cookie -containing the session ID. It will use default value for the cookie name and -domain, so will "just work" when used. +A common example is the Cookie state module, which sends the browser a +cookie containing the session ID. It will use default value for the +cookie name and domain, so will "just work" when used. =head3 Store -A Store module is used to hold all the data relating to your session, for -example the users ID, or the items for their shopping cart. You can store data -in memory (FastMmap), in a file (File) or in a database (DBI). +A Store module is used to hold all the data relating to your session, +for example the users ID, or the items for their shopping cart. You +can store data in memory (FastMmap), in a file (File) or in a database +(DBI). =head3 Authentication magic @@ -103,17 +107,27 @@ retrieve the user data for you. =head3 Using a session Once the session modules are loaded, the session is available as C<< -$c->session >>, and can be writen to and read from as a simple hash reference. +$c->session >>, and can be writen to and read from as a simple hash +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 { @@ -155,36 +169,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): - - use YAML; - # application setup - __PACKAGE__->config( YAML::LoadFile(__PACKAGE__->config->{'home'} . '/myapp.yml') ); - __PACKAGE__->setup; +Now create C in your application home: -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: @@ -203,11 +208,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 @@ -216,7 +221,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( @@ -229,7 +234,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. @@ -238,7 +243,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) @@ -257,67 +262,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: @@ -327,27 +271,29 @@ This is done in several steps: =item Verification Getting the user to identify themselves, by giving you some piece of -information known only to you and the user. Then you can assume that the user -is who they say they are. This is called B. +information known only to you and the user. Then you can assume that +the user is who they say they are. This is called B. =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 internal list of groups, -or allowed persons for the current page. +Making sure the user only accesses functions you want them to +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 =head3 Modules -The Catalyst Authentication system is made up of many interacting modules, to -give you the most flexibility possible. +The Catalyst Authentication system is made up of many interacting +modules, to give you the most flexibility possible. =head4 Credential verifiers -A Credential module tables the user input, and passes it to a Store, or some -other system, for verification. Typically, a user object is created by either -this module or the Store and made accessible by a C<< $c->user >> call. +A Credential module tables the user input, and passes it to a Store, +or some other system, for verification. Typically, a user object is +created by either this module or the Store and made accessible by a +C<< $c->user >> call. Examples: @@ -357,19 +303,19 @@ Examples: =head3 Storage backends -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. +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. 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. +A User object is created by either the storage backend or the +credential verifier, and is filled with the retrieved user information. Examples: @@ -377,44 +323,74 @@ 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 roles, have access -to which paths. +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 +roles, have access to which paths. =head3 Roles authorization -Authorization by roles is for assigning users to groups, which can then be -assigned to ACLs, or just checked when needed. +Authorization by roles is for assigning users to groups, which can +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 -suitable parameters, such as B and B, or you can pass it -these values. +$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 >>). You pass it -the name of a role to check, and it returns true if the user is a member. +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}{htpasswd} = "passwdfile"; + __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; + + 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 ( $c->login( $user, $password ) ) { + if ( $c->authenticate( username => $user, password => $password ) ) { $c->res->body( "hello " . $c->user->name ); } else { # login incorrect @@ -436,50 +412,42 @@ the name of a role to check, and it returns true if 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 -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. - -Instead of using a test database, one can simply change the authentication -store to something a bit easier to deal with in a 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. +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 this can be rather awkward, which is why it's a good +thing that the authentication framework is so flexible. -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' }, - }) - ); +Instead of using a test database, one can simply change the +authentication store to something a bit easier to deal with in a +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. -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. +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. =head3 Role Based Access Control -Under role based access control each user is allowed to perform any number of -roles. For example, at a zoo no one but specially trained personnel can enter -the moose cage (Mynd you, møøse bites kan be pretty nasti!). For example: +Under role based access control each user is allowed to perform any +number of roles. For example, at a zoo no one but specially trained +personnel can enter the moose cage (Mynd you, møøse bites kan be +pretty nasti!). For example: package Zoo::Controller::MooseCage; @@ -489,17 +457,18 @@ the moose cage (Mynd you, møøse bites kan be pretty nasti!). For example: $c->model( "Moose" )->eat( $c->req->param("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. +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 control -checks. Let's load it: +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: @@ -513,11 +482,11 @@ And now our action should look like this: } } -This checks C<< $c->user >>, and only if the user has B the roles in the -list, a true value is returned. +This checks C<< $c->user >>, and only if the user has B the roles +in the list, a true value is returned. -C has a sister method, C, which throws an exception -if any roles are missing. +C has a sister method, C, which throws an +exception if any roles are missing. Some roles that might actually make sense in, say, a forum application: @@ -533,62 +502,65 @@ moderator =back -each with a distinct task (system administration versus content administration). +each with a distinct task (system administration versus content +administration). =head3 Access Control Lists 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 to be -done automatically for us. +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 C from the entire C controller: Zoo->deny_access_unless( "/moose_cage", [qw/moose_feeder/] ); -The role list behaves in the same way as C. However, the ACL -plugin isn't limited to just interacting with the Roles plugin. We can use a -code reference instead. For example, to allow either moose trainers or moose -feeders into the moose cage, we can create a more complex check: +The role list behaves in the same way as C. However, the +ACL plugin isn't limited to just interacting with the Roles plugin. We +can use a code reference instead. For example, to allow either moose +trainers or moose feeders into the moose cage, we can create a more +complex check: Zoo->deny_access_unless( "/moose_cage", sub { my $c = shift; $c->check_roles( "moose_trainer" ) || $c->check_roles( "moose_feeder" ); }); -The more specific a role, the earlier it will be checked. Let's say moose -feeders are now restricted to only the C action, while moose -trainers get access everywhere: +The more specific a role, the earlier it will be checked. Let's say +moose feeders are now restricted to only the C action, +while moose trainers get access everywhere: Zoo->deny_access_unless( "/moose_cage", [qw/moose_trainer/] ); Zoo->allow_access_if( "/moose_cage/feed_moose", [qw/moose_feeder/]); -When the C action is accessed the second check will be made. If the -user is a C, then access will be immediately granted. Otherwise, -the next rule in line will be tested - the one checking for a C. -If this rule is not satisfied, access will be immediately denied. +When the C action is accessed the second check will be +made. If the user is a C, then access will be +immediately granted. Otherwise, the next rule in line will be tested - +the one checking for a C. If this rule is not +satisfied, access will be immediately denied. -Rules applied to the same path will be checked in the order they were added. +Rules applied to the same path will be checked in the order they were +added. -Lastly, handling access denial events is done by creating an C -private action: +Lastly, handling access denial events is done by creating an +C private action: sub access_denied : Private { my ( $self, $c, $action ) = @_; - - } -This action works much like auto, in that it is inherited across namespaces -(not like object oriented code). This means that the C action -which is B to the action which was blocked will be triggered. +This action works much like auto, in that it is inherited across +namespaces (not like object oriented code). This means that the +C action which is B to the action which was +blocked will be triggered. -If this action does not exist, an error will be thrown, which you can clean up -in your C private action instead. +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. +Also, it's important to note that if you restrict access to "/" then +C, C, etc. will also be restricted. MyApp->acl_allow_root_internals; @@ -603,17 +575,20 @@ are just the start. =head2 Using existing DBIC (etc.) classes with Catalyst -Many people have existing Model classes that they would like to use with -Catalyst (or, conversely, they want to write Catalyst models that 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: +Many people have existing Model classes that they would like to use +with Catalyst (or, conversely, they want to write Catalyst models that +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 @@ -623,9 +598,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: @@ -687,7 +694,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; } @@ -724,13 +731,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 @@ -740,8 +745,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