X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FManual%2FCookbook.pod;h=d6da3c8935013b26f78a10af51f41c7d9dd193c1;hb=ca7329b2517b19d1ccf9673fb8d48920092676b5;hp=3aed9403adefcbc76fa50c29e8a42e83c8aba2c2;hpb=816fc503ea59fc23506afda7cc5104bb152b3b83;p=catagits%2FCatalyst-Manual.git diff --git a/lib/Catalyst/Manual/Cookbook.pod b/lib/Catalyst/Manual/Cookbook.pod index 3aed940..d6da3c8 100644 --- a/lib/Catalyst/Manual/Cookbook.pod +++ b/lib/Catalyst/Manual/Cookbook.pod @@ -62,11 +62,10 @@ 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 (or C<__PACKAGE__->setup(qw/-Debug/) -). 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. +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 @@ -113,14 +112,22 @@ reference. =head3 EXAMPLE - use parent qw/Catalyst/; - __PACKAGE__->setup( qw/ + 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 { @@ -255,48 +262,7 @@ 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 parent qw/Catalyst/; - __PACKAGE__->setup (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', - }; +=head2 FIXME To restrict access to any action, you can use the C method: @@ -316,7 +282,7 @@ error if the current user does not have one of the required roles: my ( $self, $c ) = @_; $c->assert_user_roles( qw/ user admin / ); } - + =head2 Authentication/Authorization This is done in several steps: @@ -390,7 +356,7 @@ 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. @@ -403,21 +369,34 @@ the user is a member. =head3 EXAMPLE - use parent qw/Catalyst/; - __PACKAGE__->setup( qw/Authentication - Authentication::Credential::Password - Authentication::Store::Htpasswd - Authorization::Roles/); - - __PACKAGE__->config->{authentication}{htpasswd} = "passwdfile"; - - sub login : Local { + package MyApp; + use Moose; + use namespace::autoclean; + extends qw/Catalyst/; + use Catalyst qw/Authentication + Authorization::Roles/; + + __PACKAGE__->config( + 'Plugin::Authentication' => { + default => { + credential => { + class => 'Htpasswd', + # FIXME + }, + store => { + class => 'Null', + }, + }, + }, + ); + + 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 @@ -453,6 +432,7 @@ use the testing instead of production database. e.g., + # FIXME - Out of date use Catalyst::Plugin::Authentication::Store::Minimal::Backend; # Sets up the user `test_user' with password `test_pass' @@ -501,10 +481,10 @@ The Authorization::Roles plugin let's us perform role based access control checks. Let's load it: use parent qw/Catalyst/; - __PACKAGE__->setup(qw/ - Authentication # yadda yadda - Authorization::Roles - /); + use Catalyst qw/ + Authentication + Authorization::Roles + /; And now our action should look like this: @@ -648,7 +628,7 @@ inherits from DBIx::Class::Schema sub connection { my ($self, @rest) = @_; $self->next::method(@rest); - # $self is now a live My::Schema object, complete with DB connection + # $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 ]); @@ -718,7 +698,7 @@ later) and SOAP::Lite (for XMLRPCsh.pl). 3. Add the XMLRPC plugin to MyApp.pm - __PACKAGE__->setup( qw/-Debug Static::Simple XMLRPC/); + use Catalyst qw/-Debug Static::Simple XMLRPC/; 4. Add an API controller @@ -764,8 +744,6 @@ enforce a specific one. my ( $self, $c, $a, $b ) = @_; return RPC::XML::int->new( $a + $b ); } - - =head1 Views @@ -843,7 +821,7 @@ This time, the helper sets several options for us in the generated View. =over -=item +=item INCLUDE_PATH defines the directories that Template Toolkit should search for the template files. @@ -990,7 +968,7 @@ L L -=head2 Adding RSS feeds +=head2 Adding RSS feeds Adding RSS feeds to your Catalyst applications is simple. We'll see two different aproaches here, but the basic premise is that you forward to @@ -1007,7 +985,7 @@ This is the aproach used in Agave (L). $c->stash->{template}='rss.tt'; } -Then you need a template. Here's the one from Agave: +Then you need a template. Here's the one from Agave: @@ -1020,7 +998,7 @@ Then you need a template. Here's the one from Agave: [% WHILE (post = posts.next) %] [% post.title %] - [% post.formatted_teaser|html%] + [% post.formatted_teaser|html%] [% post.pub_date %] [% post.full_uri %] [% post.full_uri %] @@ -1028,7 +1006,7 @@ Then you need a template. Here's the one from Agave: [% END %] - + =head3 Using XML::Feed @@ -1058,7 +1036,7 @@ like this: } A little more code in the controller, but with this approach you're -pretty sure to get something that validates. +pretty sure to get something that validates. Note that for both of the above aproaches, you'll need to set the content type like this: @@ -1143,12 +1121,12 @@ you can set it up like this: sub render : ActionClass('RenderView') { } - sub end : Private { + sub end : Private { my ( $self, $c ) = @_; $c->forward('render'); # do stuff here } - + =head2 Action Types =head3 Introduction @@ -1193,10 +1171,12 @@ and sub my_handles : Path('/handles') { .. } -becomes +becomes http://localhost:3000/handles +See also: L + =item Local When using a Local attribute, no parameters are needed, instead, the @@ -1232,12 +1212,14 @@ matches http://localhost:3000/handles -and +and http://localhost:3000/handles_and_other_parts etc. +See also: L + =item LocalRegex A LocalRegex is similar to a Regex, except it only matches below the current @@ -1255,6 +1237,11 @@ and etc. +=item Chained + +See L for a description of how the chained +dispatch type works. + =item Private Last but not least, there is the Private attribute, which allows you @@ -1283,7 +1270,7 @@ the request object using C<< $c->req->path >>. works for all unknown URLs, in this controller namespace, or every one if put directly into MyApp.pm. -=item index +=item index The index action is called when someone tries to visit the exact namespace of your controller. If index, default and matching Path @@ -1306,7 +1293,7 @@ to the current namespace. sub begin : Private { .. } -is called once when +is called once when http://localhost:3000/bucket/(anything)? @@ -1335,14 +1322,14 @@ chain of paths up to and including the ending namespace, will be called. (In contrast, only one of the begin/end/default actions will be called, the relevant one). - package MyApp.pm; + package MyApp::Controller::Root; sub auto : Private { .. } -and +and sub auto : Private { .. } -will both be called when visiting +will both be called when visiting http://localhost:3000/bucket/(anything)? @@ -1352,16 +1339,78 @@ will both be called when visiting =head3 A word of warning -Due to possible namespace conflicts with Plugins, it is advised to -only put the pre-defined Private actions in your main MyApp.pm file, -all others should go in a Controller module. +You can put root actions in your main MyApp.pm file, but this is deprecated, +please put your actions into your Root controller. =head3 More Information -L - L +=head2 DRY Controllers with Chained actions. + +Imagine that you would like the following paths in your application: + +=over + +=item B/track/> + +Displays info on a particular track. + +In the case of a multi-volume CD, this is the track sequence. + +=item B/volume//track/> + +Displays info on a track on a specific volume. + +=back + +Here is some example code, showing how to do this with chained controllers: + + package CD::Controller; + use base qw/Catalyst::Controller/; + + sub root : Chained('/') PathPart('/cd') CaptureArgs(1) { + my ($self, $c, $cd_id) = @_; + $c->stash->{cd_id} = $cd_id; + $c->stash->{cd} = $self->model('CD')->find_by_id($cd_id); + } + + sub trackinfo : Chained('track') PathPart('') Args(0) RenderView { + my ($self, $c) = @_; + } + + package CD::Controller::ByTrackSeq; + use base qw/CD::Controller/; + + sub track : Chained('root') PathPart('track') CaptureArgs(1) { + my ($self, $c, $track_seq) = @_; + $c->stash->{track} = $self->stash->{cd}->find_track_by_seq($track_seq); + } + + package CD::Controller::ByTrackVolNo; + use base qw/CD::Controller/; + + sub volume : Chained('root') PathPart('volume') CaptureArgs(1) { + my ($self, $c, $volume) = @_; + $c->stash->{volume} = $volume; + } + + sub track : Chained('volume') PathPart('track') CaptureArgs(1) { + my ($self, $c, $track_no) = @_; + $c->stash->{track} = $self->stash->{cd}->find_track_by_vol_and_track_no( + $c->stash->{volume}, $track_no + ); + } + +Note that adding other actions (i.e. chain endpoints) which operate on a track +is simply a matter of adding a new sub to CD::Controller - no code is duplicated, +even though there are two different methods of looking up a track. + +This technique can be expanded as needed to fulfil your requirements - for example, +if you inherit the first action of a chain from a base class, then mixing in a +different base class can be used to duplicate an entire URL hieratchy at a different +point within your application. + =head2 Component-based Subrequests See L. @@ -1465,9 +1514,15 @@ the Catalyst Request object: $c->req->args([qw/arg1 arg2 arg3/]); $c->forward('/wherever'); -(See the L Flow_Control section for more +(See the L Flow_Control section for more information on passing arguments via C.) +=head2 Chained dispatch using base classes, and inner packages. + + package MyApp::Controller::Base; + use base qw/Catalyst::Controller/; + + sub key1 : Chained('/') =head1 Deployment @@ -1522,7 +1577,7 @@ to run a Catalyst app. =head4 1. Install Catalyst::Engine::Apache -You should install the latest versions of both Catalyst and +You should install the latest versions of both Catalyst and Catalyst::Engine::Apache. The Apache engines were separated from the Catalyst core in version 5.50 to allow for updates to the engine without requiring a new Catalyst release. @@ -1549,7 +1604,7 @@ Here is a basic Apache 2 configuration. PerlSwitches -I/var/www/MyApp/lib PerlModule MyApp - + SetHandler modperl PerlResponseHandler MyApp @@ -1581,7 +1636,7 @@ of your choice. SetHandler modperl PerlResponseHandler MyApp - + When running this way, it is best to make use of the C method in Catalyst for constructing correct links. @@ -1593,7 +1648,7 @@ Static files can be served directly by Apache for a performance boost. SetHandler default-handler - + This will let all files within root/static be handled directly by Apache. In a two-tiered setup, the frontend server should handle static files. The configuration to do this on the frontend will vary. @@ -1699,6 +1754,9 @@ can be used without worrying about the thread safety of your application. =head3 Cons +You may have to disable mod_deflate. If you experience page hangs with +mod_fastcgi then remove deflate.load and deflate.conf from mods-enabled/ + =head4 More complex environment With FastCGI, there are more things to monitor and more processes running @@ -1720,10 +1778,10 @@ for example, libapache2-mod-fastcgi in Debian. FastCgiServer /var/www/MyApp/script/myapp_fastcgi.pl -processes 3 Alias /myapp/ /var/www/MyApp/script/myapp_fastcgi.pl/ - + # Or, run at the root Alias / /var/www/MyApp/script/myapp_fastcgi.pl/ - + The above commands will launch 3 app processes and make the app available at /myapp/ @@ -1735,12 +1793,12 @@ server gives you much more flexibility. First, launch your app as a standalone server listening on a socket. script/myapp_fastcgi.pl -l /tmp/myapp.socket -n 5 -p /tmp/myapp.pid -d - + You can also listen on a TCP port if your web server is not on the same machine. script/myapp_fastcgi.pl -l :8080 -n 5 -p /tmp/myapp.pid -d - + You will probably want to write an init script to handle starting/stopping of the app using the pid file. @@ -1754,10 +1812,10 @@ Now, we simply configure Apache to connect to the running server. FastCgiExternalServer /tmp/myapp.fcgi -socket /tmp/myapp.socket Alias /myapp/ /tmp/myapp.fcgi/ - + # Or, run at the root Alias / /tmp/myapp.fcgi/ - + =head3 More Info L. @@ -1821,7 +1879,7 @@ Make sure mod_proxy is enabled and add: ProxyPass / http://localhost:8080/ ProxyPassReverse / http://localhost:8080/ - # This is optional if you'd like to show a custom error page + # This is optional if you'd like to show a custom error page # if the proxy is not available ErrorDocument 502 /static/error_pages/http502.html @@ -1859,6 +1917,33 @@ L, which simplifies the process greatly. From the sh % perl Makefile.PL % make catalyst_par +You can customise the PAR creation process by special "catalyst_par_*" commands +available from L. You can add these commands in your +Makefile.PL just before the line containing "catalyst;" + + #Makefile.PL example with extra PAR options + use inc::Module::Install; + + name 'MyApp'; + all_from 'lib\MyApp.pm'; + + requires 'Catalyst::Runtime' => '5.80005'; + + ... + + + catalyst_par_core(1); # bundle perl core modules in the resulting PAR + catalyst_par_multiarch(1); # build a multi-architecture PAR file + catalyst_par_classes(qw/ + Some::Additional::Module + Some::Other::Module + /); # specify additional modules you want to be included into PAR + catalyst; + + install_script glob('script/*.pl'); + auto_install; + WriteAll; + Congratulations! Your package "myapp.par" is ready, the following steps are just optional. @@ -1912,7 +1997,7 @@ B is found and served. Using the plugin is as simple as setting your use line in MyApp.pm to include: - __PACKAGE__->setup( qw/Static::Simple/); + use Catalyst qw/Static::Simple/; and already files will be served. @@ -1949,7 +2034,7 @@ Static::Simple look somewhere else, this is as easy as: MyApp->config->{static}->{include_path} = [ MyApp->config->{root}, - '/path/to/my/files' + '/path/to/my/files' ]; When you override include_path, it will not automatically append the @@ -1974,7 +2059,7 @@ be processed by Catalyst): B. This list can be replaced easily: MyApp->config->{static}->{ignore_extensions} = [ - qw/tmpl tt tt2 html xhtml/ + qw/tmpl tt tt2 html xhtml/ ]; =item Ignoring directories @@ -1997,7 +2082,7 @@ using L. In your main application class (MyApp.pm), load the plugin: - __PACKAGE__->setup( qw/-Debug FormValidator Static OtherPlugin/); + use Catalyst qw/-Debug FormValidator Static OtherPlugin/; You will also need to make sure your end method does I forward static content to the view, perhaps like this: @@ -2005,7 +2090,7 @@ static content to the view, perhaps like this: sub end : Private { my ( $self, $c ) = @_; - $c->forward( 'MyApp::View::TT' ) + $c->forward( 'MyApp::View::TT' ) unless ( $c->res->body || !$c->stash->{template} ); } @@ -2112,19 +2197,19 @@ rendered XHTML version of the source POD document. This is an ideal application for a cache because the source document changes infrequently but may be viewed many times. - __PACKAGE__->setup( qw/Cache::FileCache/); - + use Catalyst qw/Cache::FileCache/; + ... - + use File::stat; sub render_pod : Local { my ( self, $c ) = @_; - + # the cache is keyed on the filename and the modification time # to check for updates to the file. my $file = $c->path_to( 'root', '2005', '11.pod' ); my $mtime = ( stat $file )->mtime; - + my $cached_pod = $c->cache->get("$file $mtime"); if ( !$cached_pod ) { $cached_pod = do_slow_pod_rendering(); @@ -2133,7 +2218,7 @@ infrequently but may be viewed many times. } $c->stash->{pod} = $cached_pod; } - + We could actually cache the result forever, but using a value such as 12 hours allows old entries to be automatically expired when they are no longer needed. @@ -2150,26 +2235,26 @@ thing for every single user who views the page. sub front_page : Path('/') { my ( $self, $c ) = @_; - + $c->forward( 'get_news_articles' ); $c->forward( 'build_lots_of_boxes' ); $c->forward( 'more_slow_stuff' ); - + $c->stash->{template} = 'index.tt'; } We can add the PageCache plugin to speed things up. - __PACKAGE__->setup( qw/Cache::FileCache PageCache/); - + use Catalyst qw/Cache::FileCache PageCache/; + sub front_page : Path ('/') { my ( $self, $c ) = @_; - + $c->cache_page( 300 ); - + # same processing as above } - + Now the entire output of the front page, from to , will be cached for 5 minutes. After 5 minutes, the next request will rebuild the page and it will be re-cached. @@ -2182,14 +2267,14 @@ You can even get that front-end Squid proxy to help out by enabling HTTP headers for the cached page. MyApp->config->{page_cache}->{set_http_headers} = 1; - + This would now set the following headers so proxies and browsers may cache the content themselves. Cache-Control: max-age=($expire_time - time) Expires: $expire_time Last-Modified: $cache_created_time - + =head3 Template Caching Template Toolkit provides support for caching compiled versions of your @@ -2198,17 +2283,17 @@ TT will cache compiled templates keyed on the file mtime, so changes will still be automatically detected. package MyApp::View::TT; - + use strict; use warnings; use base 'Catalyst::View::TT'; - + __PACKAGE__->config( COMPILE_DIR => '/tmp/template_cache', ); - + 1; - + =head3 More Info See the documentation for each cache plugin for more details and other @@ -2229,10 +2314,10 @@ alterations. =head2 Testing -Catalyst provides a convenient way of testing your application during +Catalyst provides a convenient way of testing your application during development and before deployment in a real environment. -C makes it possible to run the same tests both locally +C makes it possible to run the same tests both locally (without an external daemon) and against a remote server via HTTP. =head3 Tests @@ -2254,7 +2339,7 @@ response. =item C<02pod.t> -Verifies that all POD is free from errors. Only executed if the C +Verifies that all POD is free from errors. Only executed if the C environment variable is true. =item C<03podcoverage.t> @@ -2296,18 +2381,18 @@ take three different arguments: =back -C returns an instance of C and C returns the +C returns an instance of C and C returns the content (body) of the response. =head3 Running tests locally mundus:~/MyApp chansen$ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib/ t/ - t/01app............ok - t/02pod............ok - t/03podcoverage....ok + t/01app............ok + t/02pod............ok + t/03podcoverage....ok All tests successful. Files=3, Tests=4, 2 wallclock secs ( 1.60 cusr + 0.36 csys = 1.96 CPU) - + C ensures that debugging is off; if it's enabled you will see debug logs between tests. @@ -2319,12 +2404,12 @@ find out more about it from the links below. =head3 Running tests remotely mundus:~/MyApp chansen$ CATALYST_SERVER=http://localhost:3000/ prove --lib lib/ t/01app.t - t/01app....ok + t/01app....ok All tests successful. Files=1, Tests=2, 0 wallclock secs ( 0.40 cusr + 0.01 csys = 0.41 CPU) -C is the absolute deployment URI of -your application. In C or C it should be the host and path +C is the absolute deployment URI of +your application. In C or C it should be the host and path to the script. =head3 C and Catalyst @@ -2423,13 +2508,13 @@ Sebastian Riedel C Danijel Milicevic C -Viljo Marrandi C +Viljo Marrandi C Marcus Ramberg C Jesse Sheidlower C -Andy Grundman C +Andy Grundman C Chisel Wright C