\n";
}
$output .= "\n";
return $output;
} # End of dump_html.
# --------------------------------------------------
sub error_mode
{
my($self, $method_name) = @_;
$method_name ||= '';
$self -> log(debug => "error_mode($method_name)");
$self -> _error_mode($method_name) if ($method_name);
return $self -> _error_mode;
} # End of error_mode.
# --------------------------------------------------
sub forward
{
my($self, $run_mode, @args) = @_;
$run_mode = defined $run_mode ? $run_mode : '';
$self -> log(debug => "forward($run_mode, ...)");
$self -> _current_run_mode($run_mode);
$self -> call_hook('forward_prerun');
return $self -> _generate_output(@args);
} # End of forward.
# --------------------------------------------------
sub _generate_output
{
my($self, @args) = @_;
$self -> log(debug => '_generate_output()');
my($is_autoload) = 0;
my($run_mode) = $self -> _current_run_mode;
my(%run_modes) = $self -> run_modes;
my($method_name);
if (exists $run_modes{$run_mode})
{
$method_name = $run_modes{$run_mode};
}
else
{
croak "Error: No such run mode: '$run_mode'\n" if (! exists $run_modes{'AUTOLOAD'});
$method_name = $run_modes{'AUTOLOAD'};
$is_autoload = 1;
}
my($output);
try
{
$output = $is_autoload ? $self -> $method_name($run_mode, @args) : $self -> $method_name(@args);
}
catch
{
my($error) = $_;
$self -> call_hook('error', $error);
if ($method_name = $self -> error_mode)
{
try
{
$output = $self -> $method_name($error);
}
catch
{
croak "Error executing the error mode method '$method_name': $_\n";
};
}
else
{
croak "Error executing run mode '$run_mode': $error\n";
}
};
return defined($output) ? $output : '';
} # End of _generate_output.
# --------------------------------------------------
sub get_callbacks
{
my($self, $type, $hook) = @_;
$type ||= '';
$hook ||= '';
$self -> log(debug => "get_callbacks($type, $hook)");
croak "Error: \$type parameter to get_callbacks() must be 'class' or 'object'\n" if ($type !~ /^(?:class|object)$/);
croak "Error: \$hook parameter to get_callbacks() must be a string\n" if (length($hook) == 0);
return $type eq 'class' ? $class_callbacks{$hook} : ${$self -> _object_callbacks}{$hook};
} # End of get_callbacks.
# --------------------------------------------------
sub get_current_runmode
{
my($self) = @_;
$self -> log(debug => 'get_current_runmode()');
return $self -> _current_run_mode;
} # End of get_current_runmode.
# --------------------------------------------------
sub header_add
{
my($self, @headers) = @_;
$self -> log(debug => 'header_add(...)');
my(%new) = ref $headers[0] eq 'HASH' ? %{$headers[0]}
: ref $headers[0] eq 'ARRAY' ? @{$headers[0]}
: scalar(@headers) % 2 == 0 ? @headers
: croak "Error: Odd number of parameters passed to header_add()\n";
my($old) = $self -> _headers;
if (scalar keys %new)
{
my($value);
for my $key (grep{ref $new{$_} eq 'ARRAY'} keys %new)
{
$value = $$old{$key};
next if (! defined $value);
$value = [$value] if (ref $value ne 'ARRAY');
$new{$key} = [@$value, @{$new{$key} }];
}
$old = {%$old, %new};
$self -> _headers($old);
}
return %$old;
} # End of header_add.
# --------------------------------------------------
sub header_props
{
my($self, @headers) = @_;
$self -> log(debug => 'header_props(...)');
if (@headers)
{
my(%new) = ref $headers[0] eq 'HASH' ? %{$headers[0]}
: ref $headers[0] eq 'ARRAY' ? @{$headers[0]}
: scalar(@headers) % 2 == 0 ? @headers
: croak "Error: Odd number of parameters passed to header_props()\n";
$self -> _headers({%new});
}
return %{$self -> _headers};
} # End of header_props.
# --------------------------------------------------
sub header_type
{
my($self, $option) = @_;
$option ||= '';
$self -> log(debug => "header_type($option)");
if ($option)
{
my(%valid) = (header => 1, none => 1, redirect => 1);
croak "Error: Invalid header type '$option'. Must be one of: " . join(', ', sort keys %valid) . "\n" if (! $valid{$option});
$self -> _header_type($option);
}
return $self -> _header_type;
} # End of header_type.
# --------------------------------------------------
sub log
{
my($self, $level, $s) = @_;
$level ||= 'info';
$s ||= '';
# We can't use $self here because add_callback can be called as a class method,
# and logging inside add_callback would then call here without initializing $self
# to be an instance. It would just be the string name of the class calling add_callback.
$myself -> logger -> log($level => $s) if ($myself && $myself -> logger);
} # End of log.
# --------------------------------------------------
sub mode_param
{
my($self, @new_options) = @_;
$self -> log(debug => 'mode_param(...)');
my($mode_source);
if (@new_options)
{
my($ref) = ref $new_options[0];
if ( ($#new_options == 0) && ($ref !~ /(?:ARRAY|HASH)/) )
{
$mode_source = $new_options[0];
}
else
{
my(%new_options) = $ref eq 'HASH' ? %{$new_options[0]}
: $ref eq 'ARRAY' ? @{$new_options[0]}
: scalar(@new_options) % 2 == 0 ? @new_options
: croak "Error: Odd number of parameters passed to mode_param()\n";
# We need defined in case someone uses a run mode of 0.
$mode_source = defined($new_options{param}) ? $new_options{param} : '';
my($index) = $new_options{path_info};
my($path_info) = $self -> query -> path_info;
if ($index && $path_info)
{
$index -= 1 if ($index > 0);
$path_info =~ s!^/!!;
$path_info = (split m|/|, $path_info)[$index] || '';
$mode_source = length $index ? {run_mode => $path_info} : $mode_source;
}
}
$self -> _run_mode_source($mode_source);
}
else
{
$mode_source = $self -> _run_mode_source;
}
return $mode_source;
} # End of mode_param.
# --------------------------------------------------
sub new_hook
{
my($self, $hook) = @_;
croak "Error: Can't use undef as a hook name\n" if (! defined $hook);
$hook = lc $hook;
$self -> log(debug => "new_hook($hook)");
$class_callbacks{$hook} ||= {};
return 1;
} # End of new_hook.
# --------------------------------------------------
sub param
{
my($self, @params) = @_;
$self -> log(debug => 'param(...)');
my(%old) = %{$self -> _params};
my($returnz);
my($value);
if (@params)
{
my(%new);
if (ref $params[0] eq 'HASH')
{
%new = %{$params[0]};
}
elsif (ref $params[0] eq 'ARRAY')
{
%new = @{$params[0]};
}
elsif (scalar @params % 2 == 0)
{
%new = @params;
$value = $params[1] if ($#params == 1);
}
elsif ($#params == 0)
{
$value = $old{$params[0]};
}
else
{
croak "Error: Odd number of parameters passed to param()\n";
}
$returnz = 'scalar';
%old = (%old, %new);
$self -> _params({%old});
}
else
{
$returnz = 'array';
}
return $returnz eq 'scalar' ? $value : keys %{$self -> _params};
} # End of param.
# --------------------------------------------------
sub prerun_mode
{
my($self, $run_mode) = @_;
$run_mode = defined($run_mode) ? $run_mode : '';
$self -> log(debug => "prerun_mode($run_mode)");
croak "Error: prerun_mode() can only be called from within cgiapp_prerun()\n" if ($self -> _prerun_mode_lock);
$self -> _current_run_mode($run_mode);
return $run_mode;
} # End of prerun_mode.
# --------------------------------------------------
sub psgi_app
{
my($self, %arg) = @_;
$self -> log(debug => 'psgi_app(...)');
return
sub
{
my($env) = @_;
if (! $arg{QUERY})
{
require CGI::PSGI;
$arg{QUERY} = CGI::PSGI -> new($env);
}
$arg{_psgi} = 1; # Required.
my($class) = $self;
$class =~ s/=HASH\(.+\)//;
return $class -> new(%arg) -> run;
};
} # End of psgi_app.
# --------------------------------------------------
sub query
{
my($self, $q) = @_;
$q ||= '';
$self -> log(debug => "_query($q)");
$self -> _query($q) if ($q);
$self -> cgiapp_get_query if (! $self -> _query);
return $self -> _query;
} # End of _query.
# --------------------------------------------------
sub redirect
{
my($self, $url, $status) = @_;
$url ||= '';
$status ||= 0;
$self -> log(debug => "redirect($url, ...)");
# If we're in the prerun phase, generate a no-op via a dummy sub.
if ($self -> _prerun_mode_lock == 0)
{
$self -> run_modes(dummy_redirect => sub{});
$self -> prerun_mode('dummy_redirect');
}
if ($status)
{
$self -> header_add(-location => $url, -status => $status);
}
else
{
$self -> header_add(-location => $url);
}
$self -> header_type('redirect');
} # End of redirect.
# --------------------------------------------------
sub run
{
my($self) = @_;
$self -> log(debug => 'run()');
my($output) = $self -> _determine_output;
if ($self -> _psgi)
{
my($status, $header) = $self -> _determine_psgi_header;
utf8::downgrade($_, 0) for @$header;
$self -> call_hook('teardown');
return [$status, $header, [$output] ];
}
else
{
my($header) = $self -> _determine_cgi_header;
utf8::downgrade($header, 0);
$output = $header . $output;
print $output if ($self -> send_output);
$self -> call_hook('teardown');
return $output;
}
} # End of run.
# --------------------------------------------------
sub run_modes
{
my($self, @new_modes) = @_;
$self -> log(debug => 'run_modes(...)');
my($old_modes) = $self -> _run_modes;
if (@new_modes)
{
$old_modes = ref $new_modes[0] eq 'HASH' ? {%$old_modes, %{$new_modes[0]} }
: ref $new_modes[0] eq 'ARRAY' ? {%$old_modes, map{($_ => $_)} @{$new_modes[0]} }
: scalar(@new_modes) % 2 == 0 ? {%$old_modes, @new_modes}
: croak "Error: Odd number of parameters passed to run_modes()\n";
$self -> _run_modes($old_modes);
}
return %$old_modes;
} # End of run_modes.
# --------------------------------------------------
sub setup
{
my($self) = @_;
$self -> log(debug => 'setup()');
} # End of setup.
# --------------------------------------------------
sub start_mode
{
my($self, $run_mode) = @_;
if ($run_mode)
{
$self -> _start_mode($run_mode = defined $run_mode ? $run_mode : '');
}
else
{
$run_mode = $self -> _start_mode;
}
$self -> log(debug => "start_mode($run_mode)");
return $self -> _start_mode;
} # End of start_mode.
# --------------------------------------------------
sub teardown
{
my($self) = @_;
$self -> log(debug => 'teardown()');
} # End of teardown.
# --------------------------------------------------
1;
=pod
=head1 NAME
CGI::Snapp - An almost back-compat fork of CGI::Application
=head1 Synopsis
In general, use as you would L, except for the differences discussed in L.
But be warned, load_tmp() and tmp_path() in particular are not supported, because they're too tied to the L way of doing things, and I prefer L.
=head1 Description
A fork of L (later L etc) in order to understand how they work in sufficient detail that I can put L etc into
production - I - as replacements for those modules.
You are I encouraged to peruse L for details of the differences between L and L.
=head1 The Flow of Control
This is a short article on which methods get called in which order. Steve Comrie has written a version for L:
L.
=head2 An Overview
If you have trouble following this explanation, consider working thru the tests (t/*.pl called by t/test.t) shipped with this distro.
Now, under normal circumstances, your CGI script receives CGI form data and accesses it via an object of type L or similar.
Let's say you have a CGI form field called 'rm', and when the user submits the form, that field has the value 'start'.
Then in the terminology of this module, and its predecessor, 'start' is called a run mode.
(In fact, 'rm' is the default name of the CGI form field this module uses to find the name of the run mode. And, when that CGI form field's name does not exist, or is empty, the
default run mode is 'start'.)
Then L uses 'start' to find which method to run to handle that run mode. The default run mode 'start' runs a method called L' implemented in L.
How does it use 'start' to find the name of the method? By examining a dispatch table (a hash), which is explained under
L. 'start' is the key, and (in the simplest case) the value is the name of a method.
Your run mode methods must all I a string or stringref of HTML to be sent to the HTTP client. You code must never write to STDOUT - that's the classic mistake most beginners make.
You can of course override the defaults just mentioned:
=over 4
=item o The default CGI form field name 'rm'
Method L allows you to change that CGI form field name from 'rm' to another string, amongst other options.
=item o The default run mode 'start'
Method L allows you to change that run mode 'start' to another string.
=item o The default association between 'start' and 'dump_html()'
Method L allows you to associate any run mode name with any method name.
=back
=head2 The Simple View
So, a basic L script is something like:
#!/usr/bin/env perl
use KillerApp;
KillerApp -> new -> run;
Here's what happens as L runs firstly 'new()' and then 'run()':
=over 4
=item o The call to new():
This calls some initialization code, which you never override (so we ignore it), and then calls, in this order:
=over 4
=item o 1: cgiapp_init(@args)
Here, @args is the array of options passed in to L.
=item o 2: setup()
=back
These 2 methods give you scope to set up anything you want before your run mode method is activated, by sub-classing L and re-implementing either or both of these methods.
For instance, if we have this inheritance structure: CGI::Snapp --> parent of --> GlobalApp --> parent of --> SpecificApp, then one or both of these methods could be
implemented in GlobalApp and/or in SpecificApp. This would allow yet other descendents of GlobalApp (in parallel with SpecificApp) to share GlobalApp's code, and at the same time
implement their own run methods.
After calling L, a call to L will return undef, since determination of the run mode only takes place during the call to L.
=item o The call to run():
This in turn calls:
=over 4
=item o 3: mode_param([@new_options])
=back
So now we know how you want run modes to be determined. See L for how to control this mechanism.
Then it calls internal code to get the name of the run mode, using - by default - the L form field parameter whose name defaults to 'rm'.
Finally, methods are called in this order:
=over 4
=item o 4: cgiapp_prerun($run_mode)
During this call (and at no other time), you can call L to change the name of the run mode which is about to be executed.
=item o 5: your_run_mode_method()
This is found via the dispatch table described at length under L
The name of the run mode is the key used to find this method name in the dispatch table (which is just a hash).
Your run mode method must return a string, or a scalarref to a string, containing the HTML to be output to the HTTP client (normally a browser of course).
See note 1 (just below) on what parameters are passed to the method.
See note 2 (just below) on what happens if the key is not present in the dispatch table.
See note 3 (just below) on what happens if the run mode method fails to run.
=item o 6: cgiapp_postrun(\$html)
A scalarref of the generated HTML is passed in to cgiapp_postrun(), which can overwrite that HTML if desired.
Now, the HTTP headers are generated, and both those headers and the HTML are sent to the HTTP client. You can stop the transmission with L.
utf8::downgrade() is used to turn off any stray UTF-8 bits on the headers.
=item o 7: teardown()
Here's where you clean up, by disconnecting from the database, or whatever.
=back
=back
=head3 Note 1: Parameters passed to your run mode method
Normally, the only parameter passed is $self, which is an object of type L or a sub-class.
However, if the method was invoked via the AUTOLOAD mechanism (note 2), the next parameter is the run mode.
Lastly, if the method was invoked via L's forward(@args), then those parameters you pass to forward() will be passed to the run mode method (after $self).
=head3 Note 2: When the run mode is not a key in the dispatch table, this algorithm is invoked
=over 4
=item o The AUTOLOAD run mode
The code checks if you have defined a run mode named 'AUTOLOAD'. If so, it's value in the dispatch table is used as the method name.
=item o Fallback
If no run mode called 'AUTOLOAD' is found, the code calls L's croak($message).
=back
=head3 Note 3: When the run mode method fails to run, this algorithm is invoked
=over 4
=item o The error hook
The method, if any, attached to the 'error' hook is called. The error message generated from the run mode method's failure is passed as the parameter, for you to utilize when deciding what
action to take.
Hooks are discussed under L just below.
=item o The error_mode method
Next, L is called. If it returns a defined value, that value is used as the name of a method to call.
=item o Fallback
Finally, if L does not return a method name, or calling that method also fails, the code calls L's croak($message).
=back
Aren't you glad that was the I view?
=head2 A More Complex View
L and before it L are designed in such a way that some of those methods are actually I aka I, and their names are looked up via hook names.
See the Wikipedia article L for a long explanation of hooks.
It works like this: A hook name is a key in a hash, and the corresponding value is a package name, which in turn points to an arrayref of method names. So, for a given hook name and
package, we can execute a series of named methods, where those names are listed in that arrayref.
The hooked methods are not expected to return anything.
Here's the default set of hooks aka (default) dispatch table. It's just a hash with fancy values per key:
my(%class_callback) =
(
error => {},
forward_prerun => {},
init => {'CGI::Snapp' => ['cgiapp_init']},
prerun => {'CGI::Snapp' => ['cgiapp_prerun']},
postrun => {'CGI::Snapp' => ['cgiapp_postrun']},
teardown => {'CGI::Snapp' => ['teardown']},
);
An explanation:
=over 4
=item o Yes, there are class-level callbacks and object-level callbacks
See L for details.
=item o The error hook
By default, there is no method attached to the 'error' hook. See L for details.
=item o The init hook
Instead of calling cgiapp_init() directly at the start of the run as alleged above, we call all those methods named as belonging to the 'init' hook, of which - here - there is just the
default one, CGI::Snapp::cgiapp_init().
=item o The prerun hook
Likewise.
=item o The postrun hook
Likewise.
=item o The teardown hook
Likewise, instead of calling teardown() directly at the finish of the run, we call all those methods named as belonging to the 'teardown' hook, starting with (the default) CGI::Snapp::teardown().
=back
Now, when I say 'all those methods', that's because you can add your own hooked methods, to enhance this process. What happens is that your hooks are pushed onto the stack of hooked methods
attached to a given hook name, and run in turn at the appropriate time.
Further, besides extending the stack of methods attached to a pre-existing hook name, you can create new hook names, and attach any number of methods to each.
The pre-defined hooks are called 'error', 'init', 'prerun', 'postrun' and 'teardown', so there is no need to call L for those.
This matter is discussed in depth under the entry for L. Also, see L and L for how hooks are named and invoked.
Sample code is in t/callback.pl, in the distro.
=head1 Distributions
This module is available as a Unix-style distro (*.tgz).
See L
for help on unpacking and installing distros.
=head1 Installation
Install L as you would for any C module:
Run:
cpanm CGI::Snapp
or run:
sudo cpan CGI::Snapp
or unpack the distro, and then either:
perl Build.PL
./Build
./Build test
sudo ./Build install
or:
perl Makefile.PL
make (or dmake or nmake)
make test
make install
=head1 Constructor and Initialization
C is called as C<< my($app) = CGI::Snapp -> new(k1 => v1, k2 => v2, ...) >>.
It returns a new object of type C.
Key-value pairs accepted in the parameter list (see corresponding methods for details
[e.g. L]):
=over 4
=item o logger => $aLoggerObject
Specify a logger compatible with L.
Default: '' (The empty string).
To clarify: The built-in calls to log() all use a log level of 'debug', so if your logger has 'maxlevel' set
to anything less than 'debug', nothing will get logged.
'maxlevel' and 'minlevel' are discussed in L and L.
Also, see L and L.
=item o PARAMS => $hashref
Provides a set of ($key => $value) pairs as initial data available to your sub-class of L via the L method.
Default: {}.
=item o send_output => $Boolean
Controls whether or not the HTML output is sent (printed) to the HTTP client.
This corresponds to L's use of $ENV{CGI_APP_RETURN_ONLY}. But check the spelling in the next line.
Default: 1 (meaning yes, send). However, if $ENV{CGI_SNAPP_RETURN_ONLY} has a Perlish true value, the default is 0.
Using 0 means you have to get the output from the return value of the L method.
=item o QUERY => $q
Provides L with a pre-created L-compatible object.
Default: ''.
However, a new L object is created at run-time if needed. See L.
=back
=head1 Methods
=head2 add_callback($hook, $option)
Adds another method to the stack of methods associated with $hook.
$hook is the name of a hook. $hook is forced to be lower-case.
Returns nothing.
That name is either pre-defined (see L) or one of your own, which you've previously set up with L.
Sample code:
# Class-level callbacks.
$class_name -> add_callback('init', \&method_1);
KillerApp -> add_callback('init', 'method_2');
# Object-level callbacks.
$app = CGI::Snapp -> new;
$app -> add_callback('init', \&method_3);
Notes:
=over 4
=item o Callback lifetimes
Class-level callbacks outlive the life of the $app object (of type L or your sub-class), by surviving for the duration of the Perl process, which, in a persistent
environment like L, L, etc, can be long enough to serve many HTTP client requests.
Object-level callbacks, however, go out of scope at the same time the $app object itself does.
=item o The class hierarchy
Callbacks can be registered by an object, or any of its parent classes, all the way up the hierarchy to L.
=item o Callback name resolution
Callback names are checked, and only the first with a given name is called. The type of callback, class or object, is ignored in this test, as it is in L.
This also means, that if there are 2 callbacks with the same name, in different classes, then still only the first is called.
Consider:
In Class A: $self -> add_callback('teardown', 'teardown_sub');
In Class B: $self -> add_callback('teardown', 'teardown_sub');
Here, because the names are the same, only one (1) teardown_sub() will be called. Which one called depends on the order in which those calls to add_callback() take place.
In Class A: $self -> add_callback('teardown', \&teardown_sub);
In Class B: $self -> add_callback('teardown', \&teardown_sub);
This time, both teardown_sub()s are called, because what's passed to add_callback() are 2 subrefs, which are memory addresses, and can't be the same for 2 different subs.
=item o Pre-defined hooks
Only the pre-defined hooks are called by L. So, if you use your own name in calling new_hook($name), you are also responsible for triggering the calls to that hook.
The pre-defined hooks are called 'error', 'init', 'prerun', 'postrun' and 'teardown', and there is no need to call L for those.
=item o Class-level callbacks
These belong to the class of the object calling L.
=item o Multiple callbacks for a given hook
If multiple I-level callbacks are added for the same hook by different classes, they will be executed in reverse-class-hierarchy order.
That it, the callback for the most derived class is executed first. This is the way normal class-hierarchy overrides work - nothing unexpected here.
If multiple I-level callbacks are added for the same hook by the same class, they will be executed in the order added, since they are pushed onto a stack (as are object-level
callbacks).
If multiple I
=item o __HEADER_PROPS => L
=item o __HEADER_TYPE => L
=item o __HTML_TMPL_CLASS => Not implemented
=item o __INSTALLED_CALLBACKS => L
=item o __IS_PSGI => _psgi()
=item o __MODE_PARAM => L
=item o __PARAMS => L
=item o __PRERUN_MODE => L
=item o __PRERUN_MODE_LOCKED => _prerun_mode_lock([$Boolean])
=item o __QUERY_OBJ => L
=item o __RUN_MODES => L
=item o __START_MODE => L
=item o __TMPL_PATH => Not implemented
=back
The leading '_' on some CGI::Snapp method names means all such methods are for the exclusive use of the author of this module.
=head3 New methods
=over 4
=item o L
=item o L
=item o L
=item o L
=item o L
=back
=head3 Deprecated methods
=over 4
=item o L
See L.
=back
=head3 Unsupported methods
=over 4
=item o html_tmpl_class()
=item o load_tmpl()
=item o run_as_psgi()
=item o tmpl_path()
=back
See below for details.
=head3 Enchanced features
=over 4
=item o Use of utf8::downgrade() to turn off utf8 bit on headers
=item o Use of Try::Tiny rather than eval
Ideally, this won't be detectable, and hence won't matter.
=item o call_hook(...) returns a hashref - keys are 'class' and 'object' - of counts of hooks actually called
=item o delete_header(A list)
See L for how to delete any number of HTTP headers.
=item o Calling the error_mode() method
This call is protected by Try::Tiny.
=item o Calling mode_param([...])
mode_param() can be called with an arrayref, as in $self -> mode_param([qw/path_info -2/]). See t/run.modes.pl for details.
=item o Calling param([...])
param() can be called with an arrayref, as in $self -> param([qw/six 6 seven 7/]). See t/params.pl for details.
=back
=head3 No special code for Apache, mod_perl or plugins
I suggest that sort of stuff is best put in sub-classes.
For the record, I don't use Apache or mod_perl. For web servers I use L, L, L and (for development) L.
As it happens, I don't use any plugins (for L) either.
So, it's not that I refuse to support them, it's just that I won't put any special code in place unless asked to do so. And then, only if it fits into my philosophy
of where this code is headed. And that includes potential re-writes of L, L and L.
=head3 Upper-case parameters to L
Yes, I know SHOUTING parameter names is ugly, but I back-compat feautures must be supported, right?. Hence L accepts PARAMS and QUERY.
=head3 Template Mangement
L contains no special processing for L, or indeed any templating system. Rationale:
There is no support because I see L's usage as a manifestation of an (understandable) design fault. If anything, TMPL_PATH should have been CONFIG_PATH.
That is, one of the methods in your sub-class - cgiapp_init(), cgiapp_prerun() or setup(), or a hook - should load a config file, and in that file is the place to put a template path,
along with all those other things typically needed: Paths to CSS and Javascript libraries, database connexion parameters, etc.
Then, each different sub-class can load a different config file, if necessary, and hence use a different set of templates. Likewise, testing and production versions of config files
can be deployed, and so on.
For example, first read in a hashref of config options (see L), and then set up a rendering engine:
use Config::Plugin::Tiny; # For config_tiny().
use Text::Xslate;
...
$self -> param
(
config => config_tiny('/some/dir/some.file.ini');
);
$self -> param
(
renderer => Text::Xslate -> new
(
input_layer => '',
path => ${$self -> param('config')}{template_path},
)
);
Then, later, use the renderer like this (in a View component of the MVC style):
my($output) =
{
div => 'order_message_div',
content => $self -> param('renderer') -> render('note.tx', $param),
};
return JSON::XS -> new -> utf8 -> encode($output);
=head2 How does add_header() differ from header_add()?
Firstly, a note about the name of header_add(). It really should have been called add_header() in the first place, just like add_callback().
After 70 years of programming, programmers should have learned that I in function/method/sub names.
I do understand the choice of header_add(): It's by analogy with header_props() and header_type(). I used to argue like that myself :-(.
OK, here's how they differ. Consider this code.
$app -> header_add(a => 1, b => [2], c => 3, d => [4]) or call add_header(same params)
$app -> header_add(a => 11, b => 22, c => [33], d => [44]) or call add_header(same params)
Output:
(a => 11, b => 22, c => [3, 33], d => [4, 44]) - header_add() - CGI::Snapp and CGI::Application
(a => [1, 11], b => [2, 22], c => [3, 33], d => [4, 44]) - add_header() - CGI::Snapp
You can see, for both modules, L I a pre-exising header when the incoming header's value is a scalar.
L's L emulates L's weird L logic here.
But, if you want to add headers without violating the L, use L.
Also, L is the counterpart of L.
For this reason, L is deprecated.
=head2 I'm confused because you called your tests t/*.pl
Well, not really. t/test.t is I test script. It runs all t/*.pl helper scripts. Run it thusly: shell> prove -Ilib -v t/
You can run any single test helper script - e.g. t/defaults.pl - like this: shell> prove -Ilib -v t/defaults.pl
=head2 Do you expect authors of plugins for CGI::App to re-write their code?
Nope. But they are free to do so...
=head2 Are you going to release any plugins?
Yes. Check out L.
=head2 How do I sub-class CGI::Snapp?
There is an example in t/subclass.pl, which uses t/lib/CGI/Snapp/SubClass.pm. The latter is:
package CGI::Snapp::SubClass;
use parent 'CGI::Snapp';
use strict;
use warnings;
use Moo;
has => verbose
(
is => 'rw',
default => sub{return 0},
required => 0,
);
our $VERSION = '1.08';
# --------------------------------------------------
1;
The steps are:
=over 4
=item o Create the file
Just copy t/lib/CGI/Snapp/SubClass.pm to get started.
=item o Declare the accessors
fieldhash my %verbose => 'verbose';
is how it's done. This means you can now have all these features available:
=over 4
=item o Use verbose when calling new()
CGI::Snapp::SubClass -> new(verbose => 1);
=item o Use verbose() as a getter
my($verbosity) = $self -> verbose;
=item o Use verbose($Boolean) as a setter
$self -> verbose(1);
=back
=back
See t/subclass.pl for how it works in practice.
=head2 How do I use my own logger object?
Study the sample code in L, which shows how to supply a L *.ini file to configure the logger via the wrapper class
L.
Also, see any test script, e.g. t/basic.pl.
=head2 What else do I need to know about logging?
The effect of logging varies depending on the stage at which it is activated.
And, your logger must be compatible with L.
If you call your sub-class of CGI::Snapp as My::App -> new(logger => $logging), then logging is turned on at the
earliest possible time. This means calls within L, to L (which calls cgiapp_init() )
and L, are logged. And since you have probably overridden setup(), you can do this in your setup():
$self -> log($level => $message); # Log anything...
Alternately, you could override L or L, and create your own logger object
within one of those.
Then you just do $self -> logger($my_logger), after which logging is immediately activated. But obviously that
means the calls to call_hook() and setup() (in new() ) will not produce any log output, because by now they have
already been run.
Nevertheless, after this point (e.g. in cgiapp_init() ), since a logger is now set up, logging will produce output.
Remember the prefix 'Local::Wines::Controller' mentioned in L?
Here's what it's cgiapp_prerun() looks like:
sub cgiapp_prerun
{
my($self) = @_;
# Can't call, since logger not yet set up.
# Well, could, but it's pointless...
#$self -> log(debug => 'cgiapp_prerun()');
$self -> param(config => Local::Config -> new(module_name => 'Local::Wines') -> get_config);
$self -> set_connector; # The dreaded DBIx::Connector.
$self -> logger(Local::Logger -> new(config => $self -> param('config') ) );
# Log the CGI form parameters.
my($q) = $self -> query;
$self -> log(info => '');
$self -> log(info => $q -> url(-full => 1, -path => 1) );
$self -> log(info => "Param: $_: " . $q -> param($_) ) for $q -> param;
# Other controllers add their own run modes.
$self -> run_modes([qw/display/]);
$self -> log(debug => 'tmpl_path: ' . ${$self -> param('config')}{template_path});
# Set up the database, the templater and the viewer.
# We pass the templater into the viewer so all views share it.
# A newer design has the controller created in the db class.
$self -> param
(
db => Local::Wines::Database -> new
(
dbh => $self -> param('connector') -> dbh,
logger => $self -> logger,
query => $q,
)
);
$self -> param
(
templater => Text::Xslate -> new
(
input_layer => '',
path => ${$self -> param('config')}{template_path},
)
);
$self -> param
(
view => Local::Wines::View -> new
(
db => $self -> param('db'),
logger => $self -> logger,
templater => $self -> param('templater'),
)
);
# Output this here so we know how far we got.
$self -> log(info => 'Session id: ' . $self -> param('db') -> session -> id);
} # End of cgiapp_prerun.
=head2 So, should I upgrade from CGI::Application to CGI::Snapp?
Well, that's up to you. Of course, if your code is not broken, don't fix it. But, as I said above, L will be going in to production in my work.
The biggest problem for you will almost certainly be lack of support for load_tmp() and tmpl_path().
Still, you're welcome to sub-class L and fix that...
=head1 Troubleshooting
=head2 It doesn't work!
Hmmm. Things to consider:
=over 4
=item o Run the *.cgi script from the command line
shell> perl httpd/cgi-bin/cgi.snapp.one.cgi
If that doesn't work, you're in b-i-g trouble. Keep reading for suggestions as to what to do next.
=item o Did you try using a logger to trace the method calls?
Pass a logger to your sub-class of L like this:
my($logger) = Log::Handler -> new;
$logger -> add
(
screen =>
{
maxlevel => 'debug',
message_layout => '%m',
minlevel => 'error',
newline => 1, # When running from the command line.
}
);
CGI::Snapp -> new(logger => $logger, ...) -> run;
Then, in your methods, just use:
$self -> log(debug => 'A string');
The entry to each method in CGI::Snapp and L is logged using this technique,
although only when maxlevel is 'debug'. Lower levels for maxlevel do not trigger logging.
See the source for details.
=item o The system Perl 'v' perlbrew
Are you using perlbrew? If so, recall that your web server will use the first line of your L script to find a Perl,
and that line probably says something like #!/usr/bin/env perl.
So, perhaps you'd better turn perlbrew off and install L and this module under the system Perl, before trying again.
=item o Generic advice
L.
=back
=head1 See Also
L
The following are all part of this set of distros:
L - A almost back-compat fork of CGI::Application
L and L - Dispatch requests to CGI::Snapp-based objects
L - A template-free demo of CGI::Snapp using just 1 run mode
L - A template-free demo of CGI::Snapp using N run modes
L - A template-free demo of CGI::Snapp using the forward() method
L - A template-free demo of CGI::Snapp using Log::Handler::Plugin::DBI
L - A wrapper around CGI::Snapp::Demo::Four, to simplify using Log::Handler::Plugin::DBI
L - A plugin which uses Config::Tiny
L - A plugin which uses Config::Tiny with 1 of N sections
L - Persistent session data management
L - A plugin for Log::Handler using Log::Hander::Output::DBI
L - A helper for Log::Hander::Output::DBI to create your 'log' table
=head1 Machine-Readable Change Log
The file Changes was converted into Changelog.ini by L.
=head1 Version Numbers
Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.
=head1 Credits
Please read L and L, since a great deal of work has gone into both of those modules.
=head1 Repository
L
=head1 Support
Email the author, or log a bug on RT:
L.
=head1 Author
L was written by Ron Savage Iron@savage.net.auE> in 2012.
Home page: L.
=head1 Copyright
Australian copyright (c) 2012, Ron Savage.
All Programs of mine are 'OSI Certified Open Source Software';
you can redistribute them and/or modify them under the terms of
The Artistic License, a copy of which is available at:
http://www.opensource.org/licenses/index.html
=cut