TRY / THROW / CATCH / FINAL
The Template Toolkit supports fully functional, nested exception
handling. The TRY directive introduces an exception handling scope
which continues until the matching END directive. Any errors that
occur within that block will be caught and can be handled by one
of the CATCH blocks defined.
[% TRY %]
...blah...blah...
[% CALL somecode %]
...etc...
[% INCLUDE someblock %]
...and so on...
[% CATCH %]
An error occurred!
[% END %]
Errors are raised as exceptions (objects of the Template::Exception
class) and contain two fields, 'type' and 'info'. The exception
'type' can be any string containing letters, numbers, '_' or '.', and
is used to indicate the kind of error that occurred. The 'info' field
contains an error message indicating what actually went wrong. Within
a catch block, the exception object is aliased to the 'error' variable.
You can access the 'type' and 'info' fields directly.
[% mydsn = 'dbi:MySQL:foobar' %]
...
[% TRY %]
[% USE DBI(mydsn) %]
[% CATCH %]
ERROR! Type: [% error.type %]
Info: [% error.info %]
[% END %]
output (assuming a non-existant database called 'foobar'):
ERROR! Type: DBI
Info: Unknown database "foobar"
The 'error' variable can also be specified by itself and will return a
string of the form "$type error - $info".
...
[% CATCH %]
ERROR: [% error %]
[% END %]
output:
ERROR: DBI error - Unknown database "foobar"
Each CATCH block may be specified with a particular exception type
denoting the kind of error that it should catch. Multiple CATCH
blocks can be provided to handle different types of exception that may
be thrown in the TRY block. A CATCH block specified without any type,
as in the previous example, is a default handler which will catch any
otherwise uncaught exceptions. This can also be specified as
[% CATCH DEFAULT %].
[% TRY %]
[% INCLUDE myfile %]
[% USE DBI(mydsn) %]
[% CALL somecode %]
...
[% CATCH file %]
File Error! [% error.info %]
[% CATCH DBI %]
[% INCLUDE database/error.html %]
[% CATCH %]
[% error %]
[% END %]
Remember that you can specify multiple directives within a single tag,
each delimited by ';'. Thus, you might prefer to write your simple
CATCH blocks more succinctly as:
[% TRY %]
...
[% CATCH file; "File Error! $error.info" %]
[% CATCH DBI; INCLUDE database/error.html %]
[% CATCH; error %]
[% END %]
or even:
[% TRY %]
...
[% CATCH file ;
"File Error! $error.info" ;
CATCH DBI ;
INCLUDE database/error.html ;
CATCH ;
error ;
END
%]
The DBI plugin throws exceptions of the 'DBI' type (in case that
wasn't already obvious). The other specific exception caught here is
of the 'file' type.
A 'file' error is automatically thrown by the Template Toolkit when it
can't find a file, or fails to load, parse or process a file that has
been requested by an INCLUDE, PROCESS, INSERT or WRAPPER directive.
If 'myfile' can't be found in the example above, the [% INCLUDE myfile
%] directive will raise a 'file' exception which is then caught by the
[% CATCH file %] block, generating the output:
File Error! myfile: not found
Note that the DEFAULT option (disabled by default) allows you to
specify a default file to be used any time a template file can't be
found. This will prevent file exceptions from ever being raised when
a non-existant file is requested (unless, of course, the DEFAULT file
doesn't exist). Errors encountered once the file has been found
(i.e. read error, parse error) will be raised as file exceptions as per
usual.
Uncaught exceptions (i.e. the TRY block doesn't have a type specific
or default CATCH handler) may be caught by enclosing TRY blocks which
can be nested indefinitely across multiple templates. If the error
isn't caught at any level then processing will stop and the Template
process() method will return a false value to the caller. The
relevant Template::Exception object can be retrieved by calling the
error() method.
[% TRY %]
...
[% TRY %]
[% INCLUDE $user.header %]
[% CATCH file %]
[% INCLUDE header %]
[% END %]
...
[% CATCH DBI %]
[% INCLUDE database/error.html %]
[% END %]
In this example, the inner TRY block is used to ensure that the first
INCLUDE directive works as expected. We're using a variable to
provide the name of the template we want to include, user.header, and
it's possible this contains the name of a non-existant template, or
perhaps one containing invalid template directives. If the INCLUDE fails
with a 'file' error then we CATCH it in the inner block and INCLUDE
the default 'header' file instead. Any DBI errors that occur within
the scope of the outer TRY block will be caught in the relevant CATCH
block, causing the 'database/error.html' template to be processed.
Note that included templates inherit all currently defined template
variable so these error files can quite happily access the 'error'
variable to retrieve information about the currently caught exception.
e.g.
'database/error.html':
<h2>Database Error</h2>
A database error has occurred: [% error.info %]
You can also specify a FINAL block. This is always processed
regardless of the outcome of the TRY and/or CATCH block. If an
exception is uncaught then the FINAL block is processed before jumping
to the enclosing block or returning to the caller.
[% TRY %]
...
[% CATCH this %]
...
[% CATCH that %]
...
[% FINAL %]
All done!
[% END %]
The output from the TRY block is left intact up to the point where an
exception occurs. For example, this template:
[% TRY %]
This gets printed
[% THROW food 'carrots' %]
This doesn't
[% CATCH food %]
culinary delights: [% error.info %]
[% END %]
generates the following output:
This gets printed
culinary delights: carrots
The CLEAR directive can be used in a CATCH or FINAL block to clear
any output created in the TRY block.
[% TRY %]
This gets printed
[% THROW food 'carrots' %]
This doesn't
[% CATCH food %]
[% CLEAR %]
culinary delights: [% error.info %]
[% END %]
output:
culinary delights: carrots
Exception types are hierarchical, with each level being separated by
the familiar dot operator. A 'DBI.connect' exception is a more
specific kind of 'DBI' error. Similarly, a 'myown.error.barf' is a
more specific kind of 'myown.error' type which itself is also a
'myown' error. A CATCH handler that specifies a general exception
type (such as 'DBI' or 'myown.error') will also catch more specific
types that have the same prefix as long as a more specific handler
isn't defined. Note that the order in which CATCH handlers are
defined is irrelevant; a more specific handler will always catch an
exception in preference to a more generic or default one.
[% TRY %]
...
[% CATCH DBI ;
INCLUDE database/error.html ;
CATCH DBI.connect ;
INCLUDE database/connect.html ;
CATCH ;
INCLUDE error.html ;
END
%]
In this example, a 'DBI.connect' error has it's own handler, a more
general 'DBI' block is used for all other DBI or DBI.* errors and a
default handler catches everything else.
Exceptions can be raised in a template using the THROW directive. The
first parameter is the exception type which doesn't need to be quoted
(but can be, it's the same as INCLUDE) followed by the relevant error
message which can be any regular value such as a quoted string,
variable, etc.
[% THROW food "Missing ingredients: $recipe.error" %]
[% THROW user.login 'no user id: please login' %]
[% THROW $myerror.type "My Error: $myerror.info" %]
It's also possible to specify additional positional or named
parameters to the THROW directive if you want to pass more than
just a simple message back as the error info field.
[% THROW food 'eggs' 'flour' msg='Missing Ingredients' %]
In this case, the error 'info' field will be a hash array containing
the named arguments, in this case 'msg' => 'Missing Ingredients',
and an 'args' item which contains a list of the positional arguments,
in this case 'eggs' and 'flour'. The error 'type' field remains
unchanged, here set to 'food'.
[% CATCH food %]
[% error.info.msg %]
[% FOREACH item = error.info.args %]
* [% item %]
[% END %]
[% END %]
This produces the output:
Missing Ingredients
* eggs
* flour
In addition to specifying individual positional arguments as
[% error.info.args.n %], the 'info' hash contains keys directly
pointing to the positional arguments, as a convenient shortcut.
[% error.info.0 %] # same as [% error.info.args.0 %]
Exceptions can also be thrown from Perl code which you've bound to
template variables, or defined as a plugin or other extension. To
raise an exception, call die() passing a reference to a
Template::Exception object as the argument. This will then be caught
by any enclosing TRY blocks from where the code was called.
use Template::Exception;
...
my $vars = {
foo => sub {
# ... do something ...
die Template::Exception->new('myerr.naughty',
'Bad, bad error');
},
};
template:
[% TRY %]
...
[% foo %]
...
[% CATCH myerr ;
"Error: $error" ;
END
%]
output:
Error: myerr.naughty error - Bad, bad error
The 'info' field can also be a reference to another object or data
structure, if required.
die Template::Exception->new('myerror', {
module => 'foo.pl',
errors => [ 'bad permissions', 'naughty boy' ],
});
Later, in a template:
[% TRY %]
...
[% CATCH myerror %]
[% error.info.errors.size or 'no';
error.info.errors.size == 1 ? ' error' : ' errors' %]
in [% error.info.module %]:
[% error.info.errors.join(', ') %].
[% END %]
Generating the output:
2 errors in foo.pl:
bad permissions, naughty boy.
You can also call die() with a single string, as is common in much
existing Perl code. This will automatically be converted to an
exception of the 'undef' type (that's the literal string 'undef',
not the undefined value). If the string isn't terminated with a
newline then Perl will append the familiar " at $file line $line"
message.
sub foo {
# ... do something ...
die "I'm sorry, Dave, I can't do that\n";
}
If you're writing a plugin, or some extension code that has the
current Template::Context in scope (you can safely skip this section
if this means nothing to you) then you can also raise an exception by
calling the context throw() method. You can pass it an
Template::Exception object reference, a pair of ($type, $info) parameters
or just an $info string to create an exception of 'undef' type.
$context->throw($e); # exception object
$context->throw('Denied'); # 'undef' type
$context->throw('user.passwd', 'Bad Password');