Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.
No matter how careful you are, things always go wrong. Errors are a fact of life. Your templates could contain bad code and fail to compile. Or you could get an error thrown from the Template Toolkit—maybe it can't find the header file you asked for. Or your back-end code could raise an error—you failed to connect to the required database. The Template Toolkit wouldn't be of much use if common errors such as these caused it to keel over and die. That's why it provides an exception-handling mechanism in the form of TRY...CATCH.
Exceptions are just a fancy way of saying errors. They're structured as objects so that an error can have a type (just a word to identify the kind of error that occurred, such as database, user, or file) and an info field that provides further information about the specifics of the error. They get thrown just like regular errors, via Perl's die, but rather than saying die 'bad apple', we say THROW bad apple.
You don't have to explicitly add code to handle errors. If you don't and an error occurs, it gets reported in the usual way. But if you know that errors might occur and you have a sensible way of recovering from them, it's good to add TRY...CATCH to do that.
Using the exception mechanism doesn't force you to worry about all errors that might occur. You can filter on the type of error and just look out for your one custom error code to catch, letting everything else pass through. Exceptions can also be nested, so you can catch them at the most appropriate level in your template.
The Template Toolkit supports fully functional, nested exception handling. The TRY directive introduces an exception-handling scope that 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 %]The previous example generates the following output (assuming a nonexistent 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 %]
The previous example generates the following 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 exceptions that may be thrown in the TRY block. A CATCH block specified without any type, as in the previous example, is a default handler that will catch any otherwise uncaught exceptions. This also can 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 previous example, 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 nonexistent 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 that can be nested indefinitely across multiple templates. If the error isn't caught at any level, 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 nonexistent template, or perhaps one containing invalid template directives. If the INCLUDE fails with a file error, 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 variables, so these error files can quite happily access the error variable to retrieve information about the currently caught exception. For example:
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, 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. For example, this template:
[% TRY %] This gets printed [% THROW food 'carrots' %] This doesn't [% CATCH food %] [% CLEAR %] culinary delights: [% error.info %] [% END %]
generates the following 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 its 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 that contains a list of the positional arguments—in this case eggs and flour. The error type field remains unchanged; here it is 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 that 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');
},
};Therefore, this template:
[% TRY %]
...
[% foo %]
...
[% CATCH myerr ;
"Error: $error" ;
END
%]produces the following 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, it can be used 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, 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), you can also raise an exception by calling the context throw( ) method. You can pass it a Template::Exception object reference, a pair of ($type, $info) parameters, or just a $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');The CLEAR directive can be used to clear the output buffer for the current enclosing block. It is most commonly used to clear the output generated from a TRY block up to the point where the error occurred.
[% TRY %] blah blah blah # this is normally left intact [% THROW some 'error' %] # up to the point of error ... [% CATCH %] [% CLEAR %] # clear the TRY output [% error %] # print error string [% END %]