X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=lib%2FMaypole.pm;h=9f1af35366433899221fa6cf1fe191eb42dd8cfa;hb=dc0c8b4a91ac443404c0a397cd53d2a78f23f97e;hp=908662f844431fdfa6ca4dc2664abb672f9a8b86;hpb=808f88dcc99bd004c98cbefb759da90512da58eb;p=maypole.git diff --git a/lib/Maypole.pm b/lib/Maypole.pm index 908662f..9f1af35 100644 --- a/lib/Maypole.pm +++ b/lib/Maypole.pm @@ -6,8 +6,9 @@ use warnings; use Maypole::Config; use Maypole::Constants; use Maypole::Headers; +use URI(); -our $VERSION = '2.10'; +our $VERSION = '2.11'; # proposed privacy conventions: # - no leading underscore - public to custom application code and plugins @@ -15,41 +16,352 @@ our $VERSION = '2.10'; # including plugins # - double leading underscore - private to the current package +=head1 NAME + +Maypole - MVC web application framework + +=head1 SYNOPSIS + +The canonical example used in the Maypole documentation is the beer database: + + package BeerDB; + use strict; + use warnings; + + # choose a frontend, initialise the config object, and load a plugin + use Maypole::Application qw/Relationship/; + + # get the empty config object created by Maypole::Application + my $config = __PACKAGE__->config; + + # basic settings + $config->uri_base("http://localhost/beerdb"); + $config->template_root("/path/to/templates"); + $config->rows_per_page(10); + $config->display_tables([qw[beer brewery pub style]]); + + # table relationships + $config->relationships([ + "a brewery produces beers", + "a style defines beers", + "a pub has beers on handpumps", + ]); + + # validation + BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] ); + BeerDB::Pub->untaint_columns( printable => [qw/name notes url/] ); + BeerDB::Style->untaint_columns( printable => [qw/name notes/] ); + BeerDB::Beer->untaint_columns( + printable => [qw/abv name price notes/], + integer => [qw/style brewery score/], + date => [ qw/date/], + ); + + # set everything up + __PACKAGE__->setup("dbi:SQLite:t/beerdb.db"); + + 1; + +=head1 DESCRIPTION + +This documents the Maypole request object. See the L, for a +detailed guide to using Maypole. + +Maypole is a Perl web application framework similar to Java's struts. It is +essentially completely abstracted, and so doesn't know anything about +how to talk to the outside world. + +To use it, you need to create a driver package which represents your entire +application. This is the C package used as an example in the manual. + +This needs to first use L which will make your package +inherit from the appropriate platform driver such as C or +C. Then, the driver calls C. This sets up the model classes +and configures your application. The default model class for Maypole uses +L to map a database to classes, but this can be changed by altering +configuration (B calling setup.) + + +=head1 DOCUMENTATION AND SUPPORT + +Note that some details in some of these resources may be out of date. + +=over 4 + +=item The Maypole Manual + +The primary documentation is the Maypole manual. This lives in the +C pod documents included with the distribution. + +=item Embedded POD + +Individual packages within the distribution contain (more or less) detailed +reference documentation for their API. + +=item Mailing lists + +There are two mailing lists - maypole-devel and maypole-users - see +http://maypole.perl.org/?MailingList + +=item The Maypole Wiki + +The Maypole wiki provides a useful store of extra documentation - +http://maypole.perl.org + +In particular, there's a FAQ (http://maypole.perl.org/?FAQ) and a cookbook +(http://maypole.perl.org/?Cookbook). Again, certain information on these pages +may be out of date. + +=item Web applications with Maypole + +A tutorial written by Simon Cozens for YAPC::EU 2005 - +http://www.droogs.org/perl/maypole/maypole-tutorial.pdf [228KB]. + +=item A Database-Driven Web Application in 18 Lines of Code + +By Paul Barry, published in Linux Journal, March 2005. + +http://www.linuxjournal.com/article/7937 + +"From zero to Web-based database application in eight easy steps". + +Maypole won a 2005 Linux Journal Editor's Choice Award +(http://www.linuxjournal.com/article/8293) after featuring in this article. + +=item Build Web apps with Maypole + +By Simon Cozens, on IBM's DeveloperWorks website, May 2004. + +http://www-128.ibm.com/developerworks/linux/library/l-maypole/ + +=item Rapid Web Application Deployment with Maypole + +By Simon Cozens, on O'Reilly's Perl website, April 2004. + +http://www.perl.com/pub/a/2004/04/15/maypole.html + +=item Authentication + +Some notes written by Simon Cozens. A little bit out of date, but still +very useful: http://www.droogs.org/perl/maypole/authentication.html + +=item CheatSheet + +There's a refcard for the Maypole (and Class::DBI) APIs on the wiki - +http://maypole.perl.org/?CheatSheet. Probably a little out of date now - it's a +wiki, so feel free to fix any errors! + +=item Plugins and add-ons + +There are a large and growing number of plugins and other add-on modules +available on CPAN - http://search.cpan.org/search?query=maypole&mode=module + +=item del.icio.us + +You can find a range of useful Maypole links, particularly to several thoughtful +blog entries, starting here: http://del.icio.us/search/?all=maypole + +=item CPAN ratings + +There are a couple of short reviews here: +http://cpanratings.perl.org/dist/Maypole + +=back + +=head1 DEMOS + +A couple of demos are available, sometimes with source code and configs. + +=over 4 + +=item http://maypole.perl.org/beerdb/ + +The standard BeerDB example, using the TT factory templates supplied in the +distribution. + +=item beerdb.riverside-cms.co.uk + +The standard BeerDB example, running on Mason, using the factory templates +supplied in the L distribution. + +=item beerfb.riverside-cms.co.uk + +A demo of L. This site is running on the set of Mason +templates included in the L distribution. See the +synopsis of L for an example driver + +=back + +=cut + __PACKAGE__->mk_classdata($_) for qw( config init_done view_object ); + __PACKAGE__->mk_accessors( qw( params query objects model_class template_args output path args action template error document_encoding content_type table - headers_in headers_out ) + headers_in headers_out stash session) ); + __PACKAGE__->config( Maypole::Config->new() ); + __PACKAGE__->init_done(0); -sub debug { 0 } +=head1 HOOKABLE METHODS + +As a framework, Maypole provides a number of B - methods that are +intended to be overridden. Some of these methods come with useful default +behaviour, others do nothing by default. Hooks include: + + Class methods + ------------- + debug + setup + setup_model + load_model_subclass + init + + Instance methods + ---------------- + start_request_hook + is_model_applicable + get_session + authenticate + exception + additional_data + preprocess_path + +=head1 CLASS METHODS + +=over 4 + +=item debug + + sub My::App::debug {1} + +Returns the debugging flag. Override this in your application class to +enable/disable debugging. + +You can also set the C flag via L. + +=cut + +sub debug { 0 } + +=item config + +Returns the L object + +=item setup + + My::App->setup($data_source, $user, $password, \%attr); + +Initialise the Maypole application and plugins and model classes - see +L. + +If your model is based on L, the C<\%attr> hashref can +contain options that are passed directly to L, to control +how the model hierarchy is constructed. + +Your application should call this B setting up configuration data via +L<"config">. + +=cut + +sub setup +{ + my $class = shift; + + $class->setup_model(@_); +} + +=item setup_model + +Called by C. This method builds the Maypole model hierarchy. + +A likely target for over-riding, if you need to build a customised model. + +This method also ensures any code in custom model classes is loaded, so you +don't need to load them in the driver. -sub setup +=cut + +sub setup_model { - my $calling_class = shift; + my $class = shift; - $calling_class = ref $calling_class if ref $calling_class; + $class = ref $class if ref $class; - my $config = $calling_class->config; + my $config = $class->config; $config->model || $config->model('Maypole::Model::CDBI'); $config->model->require or die sprintf "Couldn't load the model class %s: %s", $config->model, $@; - $config->model->setup_database($config, $calling_class, @_); + # among other things, this populates $config->classes + $config->model->setup_database($config, $class, @_); foreach my $subclass ( @{ $config->classes } ) { no strict 'refs'; unshift @{ $subclass . "::ISA" }, $config->model; - $config->model->adopt($subclass) - if $config->model->can("adopt"); + + # Load custom model code, if it exists - nb this must happen after the + # unshift, to allow code attributes to work, but before adopt(), + # in case adopt() calls overridden methods on $subclass + $class->load_model_subclass($subclass); + + $config->model->adopt($subclass) if $config->model->can("adopt"); + +# eval "use $subclass"; +# die "Error loading $subclass: $@" +# if $@ and $@ !~ /Can\'t locate \S+ in \@INC/; } } +=item load_model_subclass($subclass) + +This method is called from C. It attempts to load the +C<$subclass> package, if one exists. So if you make a customized C +package, you don't need to explicitly load it. + +If, perhaps during development, you don't want to load up custom classes, you +can override this method and load them manually. + +=cut + +sub load_model_subclass +{ + my ($class, $subclass) = @_; + + my $config = $class->config; + + # Load any external files for the model base class or subclasses + # (e.g. BeerDB/DBI.pm or BeerDB/Beer.pm) based on code borrowed from + # Maypole::Plugin::Loader and Class::DBI. + if ( $subclass->require ) + { + warn "Loaded external module for '$subclass'\n" if $class->debug > 1; + } + else + { + (my $filename = $subclass) =~ s!::!/!g; + die "Loading '$subclass' failed: $@\n" + unless $@ =~ /Can\'t locate \Q$filename\E\.pm/; + warn "Did not find external module for '$subclass'\n" + if $class->debug > 1; + } +} + +=item init + +Loads the view class and instantiates the view object. + +You should not call this directly, but you may wish to override this to add +application-specific initialisation - see L. + +=cut + sub init { my $class = shift; @@ -61,9 +373,14 @@ sub init || $config->display_tables( $class->config->tables ); $class->view_object( $class->config->view->new ); $class->init_done(1); - } +=item new + +Constructs a very minimal new Maypole request object. + +=cut + sub new { my ($class) = @_; @@ -76,10 +393,29 @@ sub new return $self; } +=item view_object + +Get/set the Maypole::View object + +=back + +=head1 INSTANCE METHODS + +=head2 Workflow + +=over 4 + +=item handler + +This method sets up the class if it's not done yet, sets some defaults and +leaves the dirty work to C. + +=cut + # handler() has a method attribute so that mod_perl will invoke # BeerDB->handler() as a method rather than a plain function # BeerDB::handler() and so this inherited implementation will be -# found. See e.g. "Practical mod_perl" by Bekman & Cholet for +# found. See e.g. "Practical mod_perl" by Bekman & Cholet for # more information sub handler : method { @@ -95,7 +431,14 @@ sub handler : method $self->get_request($req); $self->parse_location; - my $status = $self->handler_guts; + # hook useful for declining static requests e.g. images, or perhaps for + # sanitizing request parameters + my $status = $self->start_request_hook; + return $status unless $status == Maypole::Constants::OK(); + + $self->session($self->get_session); + + $status = $self->handler_guts; # moving this here causes unit test failures - need to check why # before committing the move @@ -103,25 +446,34 @@ sub handler : method return $status unless $status == OK; + # TODO: require send_output to return a status code $self->send_output; return $status; } +=item handler_guts + +This is the main request handling method and calls various methods to handle the +request/response and defines the workflow within Maypole. + +B. + +=cut + # The root of all evil sub handler_guts { my ($self) = @_; - $self->__load_model; + $self->__load_request_model; - my $applicable = __to_boolean( $self->is_applicable ); + my $applicable = $self->is_model_applicable; $self->__setup_plain_template unless $applicable; - - # We authenticate every request, needed for proper session management + my $status; - + eval { $status = $self->call_authenticate }; if ( my $error = $@ ) @@ -171,6 +523,12 @@ sub handler_guts return $self->__call_process_view; } +sub __load_request_model +{ + my ($self) = @_; + $self->model_class( $self->config->model->class_of($self, $self->table) ); +} + # is_applicable() returned false, so set up a plain template. Model processing # will be skipped, but need to remove the model anyway so the template can't # access it. @@ -213,48 +571,139 @@ sub __call_process_view return $status; } -sub __load_model +=item get_request + +You should only need to define this method if you are writing a new +Maypole backend. It should return something that looks like an Apache +or CGI request object, it defaults to blank. + +=cut + +sub get_request { } + +=item parse_location + +Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a Maypole +request. It does this by setting the C, and invoking C and +C. + +You should only need to define this method if you are writing a new Maypole +backend. + +=cut + +sub parse_location { - my ($self) = @_; - $self->model_class( $self->config->model->class_of($self, $self->table) ); + die "parse_location is a virtual method. Do not use Maypole directly; " . + "use Apache::MVC or similar"; } -# is_applicable() should return true or false, not OK or DECLINED, because -# the return value is never used as the return value from handler(). There's -# probably a lot of code out there supplying the return codes though, so -# instead of changing is_applicable() to return 0 or 1, the return value is -# passed through __to_boolean. I think it helps handler_guts() if we don't -# have multiple sets of return codes being checked for different things -drb. -sub is_applicable +=item start_request_hook + +This is called immediately after setting up the basic request. The default +method simply returns C. + +Any other return value causes Maypole to abort further processing of the +request. This is useful for filtering out requests for static files, e.g. +images, which should not be processed by Maypole or by the templating engine: + + sub start_request_hook + { + my ($r) = @_; + + return Maypole::Constants::DECLINED if $r->path =~ /\.jpg$/; + return Maypole::Constants::OK; + } + +=cut + +sub start_request_hook { Maypole::Constants::OK } + +=item is_applicable + +B as of version 2.11. If you have overridden it, +please override C instead, and change the return type +from a Maypole:Constant to a true/false value. + +Returns a Maypole::Constant to indicate whether the request is valid. + +=item is_model_applicable + +Returns true or false to indicate whether the request is valid. + +The default implementation checks that C<< $r->table >> is publicly +accessible and that the model class is configured to handle the +C<< $r->action >>. + +=cut + +sub is_model_applicable { my ($self) = @_; + # cater for applications that are using obsolete version + if ($self->can('is_applicable')) + { + warn "DEPRECATION WARNING: rewrite is_applicable to the interface ". + "of Maypole::is_model_applicable\n"; + return $self->is_applicable == OK; + } + + # Establish which tables should be processed by the model my $config = $self->config; $config->ok_tables || $config->ok_tables( $config->display_tables ); $config->ok_tables( { map { $_ => 1 } @{ $config->ok_tables } } ) if ref $config->ok_tables eq "ARRAY"; + + my $ok_tables = $config->ok_tables; + # Does this request concern a table to be processed by the model? my $table = $self->table; - warn "We don't have that table ($table).\n" - . "Available tables are: " - . join( ",", @{ $config->display_tables } ) - if $self->debug - and not $config->ok_tables->{$table} - and $self->action; # this is probably always true - - return DECLINED unless exists $config->ok_tables->{$table}; + my $ok = 0; + + if (exists $ok_tables->{$table}) + { + $ok = 1; + } - # Is it public? - return DECLINED unless $self->model_class->is_public($self->action); + if (not $ok) + { + warn "We don't have that table ($table).\n" + . "Available tables are: " + . join( ",", keys %$ok_tables ) + if $self->debug and not $ok_tables->{$table}; + + return 0; + } - return OK; + # Is the action public? + my $action = $self->action; + return 1 if $self->model_class->is_public($action); + + warn "The action '$action' is not applicable to the table $table" + if $self->debug; + + return 0; } -# *only* intended for translating the return code from is_applicable() -sub __to_boolean { $_[0] == OK ? 1 : 0 } +=item get_session + +The default method is empty. + +=cut + +sub get_session { } + +=item call_authenticate + +This method first checks if the relevant model class +can authenticate the user, or falls back to the default +authenticate method of your Maypole application. + +=cut sub call_authenticate { @@ -271,6 +720,30 @@ sub call_authenticate return $self->authenticate($self); } +=item authenticate + +Returns a Maypole::Constant to indicate whether the user is authenticated for +the Maypole request. + +The default implementation returns C + +=cut + +sub authenticate { return OK } + + +=item call_exception + +This model is called to catch exceptions, first after authenticate, then after +processing the model class, and finally to check for exceptions from the view +class. + +This method first checks if the relevant model class +can handle exceptions the user, or falls back to the default +exception method of your Maypole application. + +=cut + sub call_exception { my ($self, $error) = @_; @@ -285,324 +758,522 @@ sub call_exception return $self->exception($error); } -sub additional_data { } -sub authenticate { return OK } +=item exception + +This method is called if any exceptions are raised during the authentication or +model/view processing. It should accept the exception as a parameter and return +a Maypole::Constant to indicate whether the request should continue to be +processed. + +=cut sub exception { return ERROR } +=item additional_data + +Called before the model processes the request, this method gives you a chance to +do some processing for each request, for example, manipulating C. + +=cut + +sub additional_data { } + +=item send_output + +Sends the output and additional headers to the user. + +=cut + +sub send_output { + die "send_output is a virtual method. Do not use Maypole directly; use Apache::MVC or similar"; +} + + + + +=back + +=head2 Path processing and manipulation + +=over 4 + +=item path + +Returns the request path + +=item parse_path + +Parses the request path and sets the C, C and C +properties. Calls C before parsing path and setting properties. + +=cut + sub parse_path { my ($self) = @_; - $self->path || $self->path('frontpage'); + # Previous versions unconditionally set table, action and args to whatever + # was in @pi (or else to defaults, if @pi is empty). + # Adding preprocess_path(), and then setting table, action and args + # conditionally, broke lots of tests, hence this: + $self->$_(undef) for qw/action table args/; + $self->preprocess_path; + $self->path || $self->path('frontpage'); + my @pi = grep {length} split '/', $self->path; + + + $self->table || $self->table(shift @pi); + $self->action || $self->action( shift @pi or 'index' ); + $self->args || $self->args(\@pi); +} + +=item preprocess_path + +Sometimes when you don't want to rewrite or over-ride parse_path but +want to rewrite urls or extract data from them before it is parsed. + +This method is called after parse_location has populated the request +information and before parse_path has populated the model and action +information, and is passed the request object. + +You can set action, args or table in this method and parse_path will +then leave those values in place or populate them if not present + +=cut + +sub preprocess_path { }; + +=item make_path( %args or \%args or @args ) + +This is the counterpart to C. It generates a path to use +in links, form actions etc. To implement your own path scheme, just override +this method and C. + + %args = ( table => $table, + action => $action, + additional => $additional, # optional - generally an object ID + ); + + \%args = as above, but a ref - $self->table(shift @pi); + @args = ( $table, $action, $additional ); # $additional is optional + +C can be used as an alternative key to C. + +C<$additional> can be a string, an arrayref, or a hashref. An arrayref is +expanded into extra path elements, whereas a hashref is translated into a query +string. + +=cut + +sub make_path +{ + my $r = shift; - $self->action( shift @pi or 'index' ); + my %args; - $self->args(\@pi); -} + if (@_ == 1 and ref $_[0] and ref $_[0] eq 'HASH') + { + %args = %{$_[0]}; + } + elsif ( @_ > 1 and @_ < 4 ) + { + $args{table} = shift; + $args{action} = shift; + $args{additional} = shift; + } + else + { + %args = @_; + } + + do { die "no $_" unless $args{$_} } for qw( table action ); -# like CGI::param(), but read only -sub param -{ - my ($self, $key) = @_; + my $additional = $args{additional} || $args{id}; - return keys %{$self->params} unless defined $key; + my @add = (); - return unless exists $self->params->{$key}; + if ($additional) + { + # if $additional is a href, make_uri() will transform it into a query + @add = (ref $additional eq 'ARRAY') ? @$additional : ($additional); + } - my $val = $self->params->{$key}; + my $uri = $r->make_uri($args{table}, $args{action}, @add); - return ref $val ? @$val : ($val) if wantarray; - - return ref $val ? $val->[0] : $val; + return $uri->as_string; } -sub get_template_root {'.'} -sub get_request { } -sub parse_location { - die "Do not use Maypole directly; use Apache::MVC or similar"; -} -sub send_output { - die "Do not use Maypole directly; use Apache::MVC or similar"; -} +=item make_uri( @segments ) -# Session and Repeat Submission Handling +Make a L object given table, action etc. Automatically adds +the C. -sub make_random_id { - use Maypole::Session; - return Maypole::Session::generate_unique_id(); -} +If the final element in C<@segments> is a hash ref, C will render it +as a query string. -=head1 NAME +=cut -Maypole - MVC web application framework +sub make_uri +{ + my ($r, @segments) = @_; -=head1 SYNOPSIS + my $query = (ref $segments[-1] eq 'HASH') ? pop(@segments) : undef; + + my $base = $r->config->uri_base; + $base =~ s|/$||; + + my $uri = URI->new($base); + $uri->path_segments($uri->path_segments, grep {length} @segments); + + my $abs_uri = $uri->abs('/'); + $abs_uri->query_form($query) if $query; + return $abs_uri; +} -See L. +=item parse_args -=head1 DESCRIPTION +Turns post data and query string paramaters into a hash of C. -This documents the Maypole request object. See the L, for a -detailed guide to using Maypole. +You should only need to define this method if you are writing a new Maypole +backend. -Maypole is a Perl web application framework similar to Java's struts. It is -essentially completely abstracted, and so doesn't know anything about -how to talk to the outside world. +=cut -To use it, you need to create a package which represents your entire -application. In our example above, this is the C package. +sub parse_args +{ + die "parse_args() is a virtual method. Do not use Maypole directly; ". + "use Apache::MVC or similar"; +} -This needs to first use L which will make your package -inherit from the appropriate platform driver such as C or -C, and then call setup. This sets up the model classes and -configures your application. The default model class for Maypole uses -L to map a database to classes, but this can be changed by altering -configuration. (B calling setup.) +=item get_template_root -=head2 CLASS METHODS +Implementation-specific path to template root. -=head3 config +You should only need to define this method if you are writing a new Maypole +backend. Otherwise, see L -Returns the L object +=cut -=head3 setup +sub get_template_root {'.'} - My::App->setup($data_source, $user, $password, \%attr); +=back -Initialise the maypole application and model classes. Your application should -call this after setting configuration via L<"config"> +=head2 Request properties -=head3 init +=over 4 -You should not call this directly, but you may wish to override this to -add -application-specific initialisation. +=item model_class -=head3 new +Returns the perl package name that will serve as the model for the +request. It corresponds to the request C
attribute. -Constructs a very minimal new Maypole request object. -=head3 view_object +=item objects -Get/set the Maypole::View object +Get/set a list of model objects. The objects will be accessible in the view +templates. -=head3 debug +If the first item in C<$self-Eargs> can be Cd by the model +class, it will be removed from C and the retrieved object will be added to +the C list. See L for more information. - sub My::App::debug {1} +=item template_args -Returns the debugging flag. Override this in your application class to -enable/disable debugging. + $self->template_args->{foo} = 'bar'; -=head2 INSTANCE METHODS +Get/set a hash of template variables. -=head3 parse_location +=item stash -Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a -Maypole -request. It does this by setting the C, and invoking C -and -C. +A place to put custom application data. Not used by Maypole itself. -You should only need to define this method if you are writing a new -Maypole -backend. +=item template -=head3 path +Get/set the template to be used by the view. By default, it returns +C<$self-Eaction> -Returns the request path -=head3 parse_path +=item error -Parses the request path and sets the C, C and C
-properties +Get/set a request error + +=item output + +Get/set the response output. This is usually populated by the view class. You +can skip view processing by setting the C. -=head3 table +=item table The table part of the Maypole request path -=head3 action +=item action The action part of the Maypole request path -=head3 args +=item args A list of remaining parts of the request path after table and action have been removed -=head3 headers_in +=item headers_in A L object containing HTTP headers for the request -=head3 headers_out +=item headers_out A L object that contains HTTP headers for the output -=head3 parse_args +=item document_encoding -Turns post data and query string paramaters into a hash of C. - -You should only need to define this method if you are writing a new -Maypole -backend. - -=head3 param +Get/set the output encoding. Default: utf-8. -An accessor for request parameters. It behaves similarly to CGI::param() for -accessing CGI parameters. +=item content_type -=head3 params +Get/set the output content type. Default: text/html -Returns a hash of request parameters. The source of the parameters may vary -depending on the Maypole backend, but they are usually populated from request -query string and POST data. +=item get_protocol -B Where muliple values of a parameter were supplied, the -C -value -will be an array reference. +Returns the protocol the request was made with, i.e. https -=head3 get_template_root +=cut -Implementation-specific path to template root. +sub get_protocol { + die "get_protocol is a virtual method. Do not use Maypole directly; use Apache::MVC or similar"; +} -You should only need to define this method if you are writing a new -Maypole -backend. Otherwise, see L +=back -=head3 get_request +=head2 Request parameters -You should only need to define this method if you are writing a new -Maypole backend. It should return something that looks like an Apache -or CGI request object, it defaults to blank. +The source of the parameters may vary depending on the Maypole backend, but they +are usually populated from request query string and POST data. +Maypole supplies several approaches for accessing the request parameters. Note +that the current implementation (via a hashref) of C and C is +likely to change in a future version of Maypole. So avoid direct access to these +hashrefs: -=head3 is_applicable + $r->{params}->{foo} # bad + $r->params->{foo} # better -Returns a Maypole::Constant to indicate whether the request is valid. + $r->{query}->{foo} # bad + $r->query->{foo} # better -The default implementation checks that C<$self-Etable> is publicly -accessible -and that the model class is configured to handle the C<$self-Eaction> + $r->param('foo') # best -=head3 authenticate +=over 4 -Returns a Maypole::Constant to indicate whether the user is -authenticated for -the Maypole request. +=item param -The default implementation returns C +An accessor (get or set) for request parameters. It behaves similarly to +CGI::param() for accessing CGI parameters, i.e. -=head3 model_class + $r->param # returns list of keys + $r->param($key) # returns value for $key + $r->param($key => $value) # returns old value, sets to new value -Returns the perl package name that will serve as the model for the -request. It corresponds to the request C
attribute. - -=head3 additional_data - -Called before the model processes the request, this method gives you a -chance -to do some processing for each request, for example, manipulating -C. - -=head3 objects +=cut -Get/set a list of model objects. The objects will be accessible in the -view -templates. +sub param +{ + my ($self, $key) = (shift, shift); + + return keys %{$self->params} unless defined $key; + + return unless exists $self->params->{$key}; + + my $val = $self->params->{$key}; + + if (@_) + { + my $new_val = shift; + $self->params->{$key} = $new_val; + } + + return ref $val ? @$val : ($val) if wantarray; + + return ref $val ? $val->[0] : $val; +} -If the first item in C<$self-Eargs> can be Cd by the model -class, -it will be removed from C and the retrieved object will be added -to the -C list. See L for more information. -=head3 template_args +=item params - $self->template_args->{foo} = 'bar'; +Returns a hashref of request parameters. -Get/set a hash of template variables. +B Where muliple values of a parameter were supplied, the C value +will be an array reference. -=head3 template +=item query -Get/set the template to be used by the view. By default, it returns -C<$self-Eaction> +Alias for C. -=head3 exception +=back -This method is called if any exceptions are raised during the -authentication -or -model/view processing. It should accept the exception as a parameter and -return -a Maypole::Constant to indicate whether the request should continue to -be -processed. +=head3 Utility methods -=head3 error +=over 4 -Get/set a request error +=item redirect_request -=head3 output +Sets output headers to redirect based on the arguments provided -Get/set the response output. This is usually populated by the view -class. You -can skip view processing by setting the C. +Accepts either a single argument of the full url to redirect to, or a hash of +named parameters : -=head3 document_encoding +$r->redirect_request('http://www.example.com/path'); -Get/set the output encoding. Default: utf-8. +or -=head3 content_type +$r->redirect_request(protocol=>'https', domain=>'www.example.com', path=>'/path/file?arguments', status=>'302', url=>'..'); -Get/set the output content type. Default: text/html +The named parameters are protocol, domain, path, status and url -=head3 send_output +Only 1 named parameter is required but other than url, they can be combined as +required and current values (from the request) will be used in place of any +missing arguments. The url argument must be a full url including protocol and +can only be combined with status. -Sends the output and additional headers to the user. +=cut -=head3 call_authenticate +sub redirect_request { + die "redirect_request is a virtual method. Do not use Maypole directly; use Apache::MVC or similar"; +} -This method first checks if the relevant model class -can authenticate the user, or falls back to the default -authenticate method of your Maypole application. +=item redirect_internal_request +=cut -=head3 call_exception +sub redirect_internal_request { -This model is called to catch exceptions, first after authenticate, then after -processing the model class, and finally to check for exceptions from the view -class. +} -This method first checks if the relevant model class -can handle exceptions the user, or falls back to the default -exception method of your Maypole application. -=head3 make_random_id +=item make_random_id returns a unique id for this request can be used to prevent or detect repeat submissions. -=head3 handler - -This method sets up the class if it's not done yet, sets some -defaults and leaves the dirty work to handler_guts. +=cut -=head3 handler_guts +# Session and Repeat Submission Handling +sub make_random_id { + use Maypole::Session; + return Maypole::Session::generate_unique_id(); +} -This is the core of maypole. You don't want to know. +=back + +=head1 SEQUENCE DIAGRAMS + +See L for a detailed discussion of the sequence of +calls during processing of a request. This is a brief summary: + + INITIALIZATION + Model e.g. + BeerDB Maypole::Model::CDBI + | | + setup | | + o-------->|| | + || setup_model | setup_database() creates + ||------+ | a subclass of the Model + |||<----+ | for each table + ||| | | + ||| setup_database | | + |||--------------------->|| 'create' * + ||| ||----------> $subclass + ||| | | + ||| load_model_subclass | | + foreach |||------+ ($subclass) | | + $subclass ||||<----+ | require | + ||||--------------------------------------->| + ||| | | + ||| adopt($subclass) | | + |||--------------------->|| | + | | | + | | | + |-----+ init | | + ||<---+ | | + || | new | view_object: e.g + ||---------------------------------------------> Maypole::View::TT + | | | | + | | | | + | | | | + | | | | + | | | | + + + + HANDLING A REQUEST + + + BeerDB Model $subclass view_object + | | | | + handler | | | | + o-------->| new | | | + |-----> r:BeerDB | | | + | | | | | + | | | | | + | || | | | + | ||-----+ parse_location | | | + | |||<---+ | | | + | || | | | + | ||-----+ start_request_hook | | | + | |||<---+ | | | + | || | | | + | ||-----+ get_session | | | + | |||<---+ | | | + | || | | | + | ||-----+ handler_guts | | | + | |||<---+ | | | + | ||| class_of($table) | | | + | |||------------------------->|| | | + | ||| $subclass || | | + | |||<-------------------------|| | | + | ||| | | | + | |||-----+ is_model_applicable| | | + | ||||<---+ | | | + | ||| | | | + | |||-----+ call_authenticate | | | + | ||||<---+ | | | + | ||| | | | + | |||-----+ additional_data | | | + | ||||<---+ | | | + | ||| process | | fetch_objects + | |||--------------------------------->||-----+ | + | ||| | |||<---+ | + | ||| | || | + | ||| | || $action + | ||| | ||-----+ | + | ||| | |||<---+ | + | ||| | | | + | ||| process | | | + | |||------------------------------------------->|| template + | ||| | | ||-----+ + | ||| | | |||<---+ + | ||| | | | + | || send_output | | | + | ||-----+ | | | + | |||<---+ | | | + $status | || | | | + <------------------|| | | | + | | | | | + | X | | | + | | | | + | | | | + | | | | + + =head1 SEE ALSO -There's more documentation, examples, and a information on our mailing lists +There's more documentation, examples, and information on our mailing lists at the Maypole web site: L @@ -611,12 +1282,15 @@ L, L, L. =head1 AUTHOR -Maypole is currently maintained by Simon Flack C +Maypole is currently maintained by Aaron Trevena, David Baird, Dave Howorth and +Peter Speltz. =head1 AUTHOR EMERITUS Simon Cozens, C +Simon Flack maintained Maypole from 2.05 to 2.09 + Sebastian Riedel, C maintained Maypole from 1.99_01 to 2.04 =head1 THANKS TO