]> git.decadent.org.uk Git - maypole.git/blobdiff - lib/Maypole.pm
Refactored handler_guts - put view processing in a separate
[maypole.git] / lib / Maypole.pm
index 75d159e6ea58cfc659929efbece9989d09df8a0f..73c9e75ed61d9918a180c1b0e6aedaee3a21ae10 100644 (file)
@@ -7,11 +7,16 @@ use Maypole::Config;
 use Maypole::Constants;
 use Maypole::Headers;
 
-our $VERSION = '2.06';
+our $VERSION = '2.10';
+
+# proposed privacy conventions:
+# - no leading underscore     - public to custom application code and plugins
+# - single leading underscore - private to the main Maypole stack - *not* including plugins
+# - double leading underscore - private to the current package
 
 __PACKAGE__->mk_classdata($_) for qw( config init_done view_object );
 __PACKAGE__->mk_accessors(
-    qw( ar params query objects model_class template_args output path
+    qw( params query objects model_class template_args output path
         args action template error document_encoding content_type table
         headers_in headers_out )
 );
@@ -20,23 +25,23 @@ __PACKAGE__->init_done(0);
 
 sub debug { 0 }
 
-sub setup {
+sub setup 
+{
     my $calling_class = shift;
+    
     $calling_class = ref $calling_class if ref $calling_class;
-    {
-        no strict 'refs';
-        no warnings 'redefine';
-
-        # Naughty.
-        *{ $calling_class . "::handler" } =
-          sub { Maypole::handler( $calling_class, @_ ) };
-    }
+    
     my $config = $calling_class->config;
-    $config->model || $config->model("Maypole::Model::CDBI");
-    $config->model->require;
-    die "Couldn't load the model class $config->model: $@" if $@;
-    $config->model->setup_database( $config, $calling_class, @_ );
-    for my $subclass ( @{ $config->classes } ) {
+    
+    $config->model || $config->model('Maypole::Model::CDBI');
+    
+    $config->model->require or die 
+        "Couldn't load the model class $config->{model}: $@";
+    
+    $config->model->setup_database($config, $calling_class, @_);
+    
+    foreach my $subclass ( @{ $config->classes } ) 
+    {
         no strict 'refs';
         unshift @{ $subclass . "::ISA" }, $config->model;
         $config->model->adopt($subclass)
@@ -44,7 +49,8 @@ sub setup {
     }
 }
 
-sub init {
+sub init 
+{
     my $class  = shift;
     my $config = $class->config;
     $config->view || $config->view("Maypole::View::TT");
@@ -57,10 +63,16 @@ sub init {
 
 }
 
-sub handler {
-
+# 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
+# more information <http://modperlbook.org/html/ch25_01.html>
+sub handler : method 
+{
     # See Maypole::Workflow before trying to understand this.
-    my ( $class, $req ) = @_;
+    my ($class, $req) = @_;
+    
     $class->init unless $class->init_done;
 
     # Create the request object
@@ -68,69 +80,118 @@ sub handler {
         template_args => {},
         config        => $class->config
     }, $class;
+    
     $r->headers_out(Maypole::Headers->new);
+    
     $r->get_request($req);
+    
     $r->parse_location();
+    
     my $status = $r->handler_guts();
+    
     return $status unless $status == OK;
+    
     $r->send_output;
+    
     return $status;
 }
 
 # The root of all evil
-sub handler_guts {
-    my $r = shift;
-    $r->model_class( $r->config->model->class_of( $r, $r->{table} ) );
+sub handler_guts 
+{
+    my ($r) = @_;
+    
+    $r->__load_model;
 
     my $applicable = $r->is_applicable;
-    unless ( $applicable == OK ) {
-
+    
+    unless ( $applicable == OK ) 
+    {
         # It's just a plain template
-        delete $r->{model_class};
-        $r->{path} =~ s{/$}{};    # De-absolutify
-        $r->template( $r->{path} );
+        $r->model_class(undef);
+        
+        my $path = $r->path;
+        $path =~ s{/$}{};    # De-absolutify
+        $r->path($path);
+        
+        $r->template($r->path);
     }
 
     # We authenticate every request, needed for proper session management
     my $status;
+    
     eval { $status = $r->call_authenticate };
-    if ( my $error = $@ ) {
+    
+    if ( my $error = $@ ) 
+    {
         $status = $r->call_exception($error);
-        if ( $status != OK ) {
+        
+        if ( $status != OK ) 
+        {
             warn "caught authenticate error: $error";
-            return $r->debug ? $r->view_object->error( $r, $error ) : ERROR;
+            return $r->debug ? $r->view_object->error($r, $error) : ERROR;
         }
     }
-    if ( $r->debug and $status != OK and $status != DECLINED ) {
+    
+    if ( $r->debug and $status != OK and $status != DECLINED ) 
+    {
         $r->view_object->error( $r,
             "Got unexpected status $status from calling authentication" );
     }
+    
     return $status unless $status == OK;
 
     # We run additional_data for every request
     $r->additional_data;
-    if ( $applicable == OK ) {
+    
+    if ( $applicable == OK ) 
+    {
         eval { $r->model_class->process($r) };
-        if ( my $error = $@ ) {
+        
+        if ( my $error = $@ ) 
+        {
             $status = $r->call_exception($error);
-            if ( $status != OK ) {
+            if ( $status != OK ) 
+            {
                 warn "caught model error: $error";
-                return $r->debug ? $r->view_object->error( $r, $error ) : ERROR;
+                return $r->debug ? $r->view_object->error($r, $error) : ERROR;
             }
         }
     }
-    if ( !$r->{output} ) {    # You might want to do it yourself
-        eval { $status = $r->view_object->process($r) };
-        if ( my $error = $@ ) {
-            $status = $r->call_exception($error);
-            if ( $status != OK ) {
-                warn "caught view error: $error" if $r->debug;
-                return $r->debug ? $r->view_object->error( $r, $error ) : ERROR;
-            }
+    
+    # unusual path - perhaps output has been set to an error message
+    return OK if $r->output;
+    
+    # normal path - no output has been generated yet
+    return $r->__call_process_view;
+}
+
+sub __call_process_view
+{
+    my ($r) = @_;
+    
+    my $status;
+    
+    eval { $status = $r->view_object->process($r) };
+    
+    if ( my $error = $@ ) 
+    {
+        $status = $r->call_exception($error);
+        
+        if ( $status != OK ) 
+        {
+            warn "caught view error: $error" if $r->debug;
+            return $r->debug ? $r->view_object->error($r, $error) : ERROR;
         }
-        return $status;
     }
-    else { return OK; }
+    
+    return $status;
+}
+
+sub __load_model
+{
+    my ($r) = @_;
+    $r->model_class( $r->config->model->class_of($r, $r->table) );
 }
 
 sub is_applicable {
@@ -186,8 +247,7 @@ sub exception { return ERROR }
 sub parse_path {
     my $self = shift;
     $self->{path} ||= "frontpage";
-    my @pi = split /\//, $self->{path};
-    shift @pi while @pi and !$pi[0];
+    my @pi = $self->{path} =~ m{([^/]+)/?}g;
     $self->{table}  = shift @pi;
     $self->{action} = shift @pi;
     $self->{action} ||= "index";
@@ -223,6 +283,13 @@ sub send_output {
     die "Do not use Maypole directly; use Apache::MVC or similar";
 }
 
+# Session and Repeat Submission Handling
+
+sub make_random_id {
+    use Maypole::Session;
+    return Maypole::Session::generate_unique_id();
+}
+
 =head1 NAME
 
 Maypole - MVC web application framework
@@ -236,7 +303,7 @@ See L<Maypole::Application>.
 This documents the Maypole request object. See the L<Maypole::Manual>, for a
 detailed guide to using Maypole.
 
-Maypole is a Perl web application framework to Java's struts. It is 
+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.
 
@@ -465,6 +532,9 @@ 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
+
+returns a unique id for this request can be used to prevent or detect repeat submissions.
 
 =head3 handler