NAME¶
Template::Manual::Internals - Template Toolkit internals
Introduction¶
This section of the documentation is aimed at developers wishing to know more
about how the Template Toolkit works on the inside in order to extend or adapt
it to their own needs.
If that doesn't sound like you then you probably don't need to read this. There
is no test afterwards.
Outside Looking In¶
The Template module is simply a front end module which creates and uses a
Template::Service and pipes the output wherever you want it to go
("STDOUT" by default, or maybe a file, scalar, etc). The
"Apache::Template" module (available separately from CPAN) is
another front end. That creates a "Template::Service::Apache"
object, calls on it as required and sends the output back to the relevant
"Apache::Request" object.
These front-end modules are really only there to handle any specifics of the
environment in which they're being used. The "Apache::Template"
front end, for example, handles "Apache::Request" specifics and
configuration via the
httpd.conf. The regular Template front-end deals
with "STDOUT", variable refs, etc. Otherwise it is Template::Service
(or subclass) which does all the work.
The Template::Service module provides a high-quality template delivery service,
with bells, whistles, signed up service level agreement and a 30-day no
quibble money back guarantee. "Have a good time, all the time",
that's our motto.
Within the lower levels of the Template Toolkit, there are lots of messy details
that we generally don't want to have to worry about most of the time. Things
like templates not being found, or failing to parse correctly, uncaught
exceptions being thrown, missing plugin modules or dependencies, and so on.
Template::Service hides that all away and makes everything look simple to the
outsider. It provides extra features, like "PRE_PROCESS",
"PROCESS" and "POST_PROCESS", and also provides the error
recovery mechanism via "ERROR". You ask it to process a template and
it takes care of everything for you. The "Template::Service::Apache"
module goes a little bit further, adding some extra headers to the
Apache::Request, setting a few extra template variables, and so on.
For the most part, the job of a service is really just one of scheduling and
dispatching. It receives a request in the form of a call to its
process() method and schedules the named template specified as an
argument, and possibly several other templates ("PRE_PROCESS", etc)
to be processed in order. It doesn't actually process the templates itself,
but instead makes a
process() call against a Template::Context object.
Template::Context is the runtime engine for the Template Toolkit - the module
that hangs everything together in the lower levels of the Template Toolkit and
that one that does most of the real work, albeit by crafty delegation to
various other friendly helper modules.
Given a template name (or perhaps a reference to a scalar or file handle) the
context
process() method must load and compile, or fetch a cached copy
of a previously compiled template, corresponding to that name. It does this by
calling on a list of one or more Template::Provider objects (the
"LOAD_TEMPLATES" posse) who themselves might get involved with a
Template::Parser to help turn source templates into executable Perl code (but
more on that later).
Thankfully, all of this complexity is hidden away behind a simple
template() method. You call it passing a template name as an argument,
and it returns a compiled template in the form of a Template::Document object,
or otherwise raises an exception.
A Template::Document is a thin object wrapper around a compiled template
subroutine. The object implements a
process() method which performs a
little bit of housekeeping and then calls the template subroutine. The object
also defines template metadata (defined in "[% META ... %]"
directives) and has a
block() method which returns a hash of any
additional "[% BLOCK xxxx %]" definitions found in the template
source.
So the context fetches a compiled document via its own
template() method
and then gets ready to process it. It first updates the stash (the place where
template variables get defined - more on that shortly) to set any template
variable definitions specified as the second argument by reference to hash
array. Then, it calls the document
process() method, passing a
reference to itself, the context object, as an argument. In doing this, it
provides itself as an object against which template code can make callbacks to
access runtime resources and Template Toolkit functionality.
What we're trying to say here is this: not only does the Template::Context
object receive calls from the
outside, i.e. those originating in user
code calling the
process() method on a Template object, but it also
receives calls from the
inside, i.e. those originating in template
directives of the form "[% PROCESS template %]".
Before we move on to that, here's a simple structure diagram showing the outer
layers of the Template Toolkit heading inwards, with pseudo code annotations
showing a typical invocation sequence.
,--------.
| Caller | use Template;
`--------' my $tt = Template->new( ... );
| $tt->process($template, \%vars);
| Outside
- - - | - - - - - - - - - - - - - - - - - - - - - - - - - - - - T T
| package Template; Inside
V
+----------+ sub process($template, \%vars) {
| Template | $out = $self->SERVICE->process($template, $vars);
+----------+ print $out or send it to $self->OUTPUT;
| }
|
| package Template::Service;
|
| sub process($template, \%vars) {
| try {
+----------+ foreach $p in @self->PRE_PROCESS
| Service | $self->CONTEXT->process($p, $vars);
+----------+
| $self->CONTEXT->process($template, $vars);
|
| foreach $p @self->POST_PROCESS
| $self->CONTEXT->process($p, $vars);
| }
| catch {
| $self->CONTEXT->process($self->ERROR);
| }
| }
|
V package Template::Context;
+----------+
| Context | sub process($template, \%vars) {
+----------+ # fetch compiled template
| $template = $self->template($template)
| # update stash
| $self->STASH->update($vars);
| # process template
| $template->process($self)
| }
V
+----------+ package Template::Document;
| Document |
+----------+ sub process($context) {
$output = &{ $self->BLOCK }($context);
}
Inside Looking Out¶
To understand more about what's going on in these lower levels, we need to look
at what a compiled template looks like. In fact, a compiled template is just a
regular Perl sub-routine. Here's a very simple one.
sub my_compiled_template {
return "This is a compiled template.\n";
}
You're unlikely to see a compiled template this simple unless you wrote it
yourself but it is entirely valid. All a template subroutine is obliged to do
is return some output (which may be an empty of course). If it can't for some
reason, then it should raise an error via "die()".
sub my_todo_template {
die "This template not yet implemented\n";
}
If it wants to get fancy, it can raise an error as a Template::Exception object.
An exception object is really just a convenient wrapper for the
'"type"' and '"info"' fields.
sub my_solilique_template {
die (Template::Exception->new('yorrick', 'Fellow of infinite jest'));
}
Templates generally need to do a lot more than just generate static output or
raise errors. They may want to inspect variable values, process another
template, load a plugin, run a filter, and so on. Whenever a template
subroutine is called, it gets passed a reference to a Template::Context
object. It is through this context object that template code can access the
features of the Template Toolkit.
We described earlier how the Template::Service object calls on Template::Context
to handle a
process() request from the
outside. We can make a
similar request on a context to process a template, but from within the code
of another template. This is a call from the
inside.
sub my_process_template {
my $context = shift;
my $output = $context->process('header', { title => 'Hello World' })
. "\nsome content\n"
. $context->process('footer');
}
This is then roughly equivalent to a source template something like this:
[% PROCESS header
title = 'Hello World'
%]
some content
[% PROCESS footer %]
Template variables are stored in, and managed by a Template::Stash object. This
is a blessed hash array in which template variables are defined. The object
wrapper provides
get() and
set() method which implement all the
magical.variable.features of the Template Toolkit.
Each context object has its own stash, a reference to which can be returned by
the appropriately named
stash() method. So to print the value of some
template variable, or for example, to represent the following source template:
<title>[% title %]</title>
we might have a subroutine definition something like this:
sub {
my $context = shift;
my $stash = $context->stash();
return '<title>' . $stash->get('title') . '</title>';
}
The stash
get() method hides the details of the underlying variable
types, automatically calling code references, checking return values, and
performing other such tricks. If '"title"' happens to be bound to a
subroutine then we can specify additional parameters as a list reference
passed as the second argument to
get().
[% title('The Cat Sat on the Mat') %]
This translates to the stash call:
$stash->get([ 'title', ['The Cat Sat on the Mat'] ]);
Dotted compound variables can be requested by passing a single list reference to
the "get()" method in place of the variable name. Each pair of
elements in the list should correspond to the variable name and reference to a
list of arguments for each dot-delimited element of the variable.
[% foo(1, 2).bar(3, 4).baz(5) %]
is thus equivalent to
$stash->get([ foo => [1,2], bar => [3,4], baz => [5] ]);
If there aren't any arguments for an element, you can specify an empty, zero or
null argument list.
[% foo.bar %]
$stash->get([ 'foo', 0, 'bar', 0 ]);
The
set() method works in a similar way. It takes a variable name and a
variable value which should be assigned to it.
[% x = 10 %]
$stash->set('x', 10);
[% x.y = 10 %]
$stash->set([ 'x', 0, 'y', 0 ], 10);
So the stash gives us access to template variables and the context provides the
higher level functionality.
Alongside the
process() method lies the
include() method. Just as
with the "PROCESS" / "INCLUDE" directives, the key
difference is in variable localisation. Before processing a template, the
"process()" method simply updates the stash to set any new variable
definitions, overwriting any existing values. In contrast, the
"include()" method creates a copy of the existing stash, in a
process known as
cloning the stash, and then uses that as a temporary
variable store. Any previously existing variables are still defined, but any
changes made to variables, including setting the new variable values passed
aas arguments will affect only the local copy of the stash (although note that
it's only a shallow copy, so it's not foolproof). When the template has been
processed, the "include()" method restores the previous variable
state by
decloning the stash.
The context also provides an
insert() method to implement the
"INSERT" directive, but no "wrapper()" method. This
functionality can be implemented by rewriting the Perl code and calling
"include()".
[% WRAPPER foo -%]
blah blah [% x %]
[%- END %]
$context->include('foo', {
content => 'blah blah ' . $stash->get('x'),
});
Other than the template processing methods "process()",
"include()" and "insert()", the context defines methods
for fetching plugin objects,
plugin(), and filters,
filter().
# TT USE directive
[% USE foo = Bar(10) %]
# equivalent Perl
$stash->set('foo', $context->plugin('Bar', [10]));
# TT FILTER block
[% FILTER bar(20) %]
blah blah blah
[% END %]
# equivalent Perl
my $filter = $context->filter('bar', [20]);
&$filter('blah blah blah');
Pretty much everything else you might want to do in a template can be done in
Perl code. Things like "IF", "UNLESS", "FOREACH"
and so on all have direct counterparts in Perl.
# TT IF directive
[% IF msg %]
Message: [% msg %]
[% END %];
# equivalent Perl
if ($stash->get('msg')) {
$output .= 'Message: ';
$output .= $stash->get('msg');
}
The best way to get a better understanding of what's going on underneath the
hood is to set the $Template::Parser::DEBUG flag to a true value and start
processing templates. This will cause the parser to print the generated Perl
code for each template it compiles to "STDERR". You'll probably also
want to set the $Template::Directive::PRETTY option to have the Perl
pretty-printed for human consumption.
use Template;
use Template::Parser;
use Template::Directive;
$Template::Parser::DEBUG = 1;
$Template::Directive::PRETTY = 1;
my $template = Template->new();
$template->process(\*DATA, { cat => 'dog', mat => 'log' });
__DATA__
The [% cat %] sat on the [% mat %]
The output sent to "STDOUT" remains as you would expect:
The dog sat on the log
The output sent to "STDERR" would look something like this:
compiled main template document block:
sub {
my $context = shift || die "template sub called without context\n";
my $stash = $context->stash;
my $output = '';
my $error;
eval { BLOCK: {
$output .= "The ";
$output .= $stash->get('cat');
$output .= " sat on the ";
$output .= $stash->get('mat');
$output .= "\n";
} };
if ($@) {
$error = $context->catch($@, \$output);
die $error unless $error->type eq 'return';
}
return $output;
}
Please feel free to hack on the Template Toolkit. If you find a bug that needs
fixing, if you have an idea for something that's missing, or you feel inclined
to tackle something on the TODO list, then by all means go ahead and do it!
If you're contemplating something non-trivial then you'll probably want to bring
it up on the mailing list first to get an idea about the current state of
play, find out if anyone's already working on it, and so on.
When you start to hack on the Template Toolkit, please make sure you start from
the latest developer release. Stable releases are uploaded to CPAN and have
all-numerical version numbers, e.g. 2.04, 2.05. Developer releases are
available from the Template Toolkit web site and have a character suffix on
the version, e.g. 2.04a, 2.04b, etc.
Once you've made your changes, please remember to update the test suite by
adding extra tests to one of the existing test scripts in the "t"
sub-directory, or by adding a new test script of your own. And of course, run
"make test" to ensure that all the tests pass with your new code.
Don't forget that any files you do add will need to be added to the MANIFEST.
Running "make manifest" will do this for you, but you need to make
sure you haven't got any other temporary files lying around that might also
get added to it.
Documentation is often something that gets overlooked but it's just as important
as the code. If you're adding a new module, a plugin module, for example, then
it's OK to include the POD documentation in with the module, but
please
write it all in one piece at the end of the file,
after the code (just
look at any other "Template::*" module for an example). It's a
religious issue, I know, but I have a strong distaste for POD documentation
interspersed throughout the code. In my not-so-humble opinion, it makes both
the code and the documentation harder to read (same kinda problem as embedding
Perl in HTML).
To share your changes with the rest of the world, you'll need to prepare a patch
file. To do this you should have 2 directories side-by-side, one which is the
original, unmodified distribution directory for the latest developer release,
and the other is a copy of that same directory which includes your changes.
The following example shows a typical hacking session. First we unpack the
latest developer release.
$ tar zxf Template-Toolkit-2.05c.tar.gz
At this point, it's a good idea to rename the directory to give some indicate of
what it contains.
$ mv Template-Toolkit-2.05c Template-Toolkit-2.05c-abw-xyz-hack
Then go hack!
$ cd Template-Toolkit-2.05c-abw-xyz-hack
[ hacking ]
$ cd ..
When you're all done and ready to prepare a patch, unpack the distribution
archive again so that you've got the original to "diff" against your
new code.
$ tar zxf Template-Toolkit-2.05c.tar.gz
You should now have an original distribution directory and a modified version of
that same directory, side-by-side.
$ ls
Template-Toolkit-2.05c Template-Toolkit-2.05c-abw-xyz-hack
Now run "diff" and save the output into an appropriately named patch
file.
$ diff -Naur Template-Toolkit-2.05c Template-Toolkit-2.05c-abw-xyz-hack > patch-TT205c-abw-xyz-hack
You can then post the generated patch file to the mailing list, describing what
it does, why it does it, how it does it and any other relevant information.
If you want to apply someone else's patch then you should start with the same
original distribution source on which the patch is based. From within the root
of the distribution, run "patch" feeding in the patch file as
standard input. The '"p1"' option is required to strip the first
element of the path name (e.g. "Template-Toolkit-2.05c/README"
becomes "README" which is then the correct path).
$ tar zxf Template-Toolkit-2.05c.tar.gz
$ cd Template-Toolkit-2.05c
$ patch -p1 < ../patch-TT205c-abw-xyz-hack
The output generated by "patch" should be something like the
following:
patching file README
patching file lib/Template.pm
patching file lib/Template/Provider.pm
patching file t/provider.t