NAME
Template::EmbeddedPerl::Tutorial - Build a Contacts application step by step
DESCRIPTION
This tutorial builds a small, framework-neutral Contacts application. It uses in-memory data so that the templates, composition rules, and diagnostics are the only moving parts.
The complete runnable application is included with the distribution. From the distribution root, run:
perl examples/contacts/untyped/app.pl
The executable source and its exact HTML output are checked by t/cookbook_examples.t. Read this tutorial in order, then use the checked-in files as the complete working version of each fragment.
FIRST TEMPLATE
The smallest useful template renders this visible result:
Hello, Ada!
Compile a string once, then render it with ordinary positional arguments:
use Template::EmbeddedPerl;
my $compiled = Template::EmbeddedPerl->from_string(
'Hello, <%= shift %>!',
);
my $output = $compiled->render('Ada');
The <%= ... %> form evaluates a Perl expression and appends its value to the output. shift reads the first value passed to render. The compiled object can be rendered again with another value without reparsing the template.
THE CONTACTS APPLICATION
The complete application prints this page when it is run:
<!doctype html>
<html>
<head>
<title>Contacts</title>
<meta name="section" content="contacts">
</head>
<body>
<section class="page">
<main>
<h1>CONTACTS</h1>
<p class="badge">2 contacts</p>
<ul>
<li data-root="Contacts" data-parent="Contacts"><strong><Ada></strong> ada@example.test</li>
<li data-root="Contacts" data-parent="Contacts"><strong>Grace</strong> grace@example.test</li>
</ul>
</main>
</section>
</body>
</html>
Set up the checked-in example
The runnable newcomer endpoint has this exact tree:
examples/contacts/
untyped/
app.pl
lib/
Contacts/
Untyped/
App.pm
templates/
contacts/
item.epl
layouts/
application.epl
pages/
contacts.epl
Run perl examples/contacts/untyped/app.pl from the distribution root. The app.pl entry point only constructs Contacts::Untyped::App, renders it, and prints the resulting HTML.
The application keeps its deterministic Contacts data in memory. The checked-in fixture is:
Fragment: application fixture
sub contacts {
return [
{name => '<Ada>', email => 'ada@example.test'},
{name => 'Grace', email => 'grace@example.test'},
];
}
The checked-in Contacts::Untyped::App is intentionally a small application boundary. Its constructor creates one engine with the template directory, smart lines, and automatic HTML escaping enabled:
Fragment:
sub new {
my ($class, %args) = @_;
my $root = $args{root} || File::Spec->rel2abs(
File::Spec->catdir(dirname(__FILE__), qw(.. .. ..)),
);
my $self = bless {
root => $root,
heading_calls => 0,
}, $class;
$self->{engine} = Template::EmbeddedPerl->new(
directories => [File::Spec->catdir($root, 'templates')],
smart_lines => 1,
auto_escape => 1,
use_cache => 1,
helpers => {
display_heading => sub {
my ($engine, $value) = @_;
$self->{heading_calls}++;
return uc $value;
},
},
);
return $self;
}
The public rendering role is equally small. It supplies the in-memory contacts, accepts named overrides, and delegates to the root page:
Fragment:
sub render {
my ($self, %args) = @_;
return $self->{engine}->from_file('pages/contacts')->render(
contacts => $self->contacts,
%args,
);
}
Run the exact application with perl examples/contacts/untyped/app.pl. Its new method accepts root => $path, which makes the example simple to execute from tests or a different working directory.
Directories are searched in order. The first matching template wins, so put application overrides before shared template directories.
SMART LINES AND NAMED ARGUMENTS
The root Contacts page begins with declarations that render the following visible heading and badge:
<h1>CONTACTS</h1>
<p class="badge">2 contacts</p>
With smart_lines => 1, a line beginning with % is a Perl directive and a line beginning with %= outputs an expression. The root page uses these exact declarations:
Fragment:
% args $contacts, $title = 'Contacts', $heading = sub { display_heading($title) }
% layout 'layouts/application', title => $title
<h1><%= $heading %></h1>
<p class="badge"><%= scalar @$contacts %> contacts</p>
The args directive declares named render arguments. It must be the first executable directive in a template. Once it is present, call render with named key/value pairs rather than depending on positional order:
Fragment:
$page->render(
contacts => $app->contacts,
title => 'Directory',
);
DEFAULTS AND LAZY DEFAULTS
For the Contacts page, omitting title and heading produces the visible default:
<h1>CONTACTS</h1>
The declaration has three kinds of argument:
Fragment:
% args $contacts, $title = 'Contacts', $heading = sub { display_heading($title) }
$contacts has no default, so it is required. The application always passes it. $title has a scalar default and becomes Contacts when absent. The coderef default for $heading is lazy: it runs only when heading is absent, after earlier arguments such as $title have been established.
These calls show the complete behavior:
Fragment:
$page->render(contacts => $contacts); # lazy default runs
$page->render(contacts => $contacts, title => 'People'); # heading is PEOPLE
$page->render(contacts => $contacts, heading => 'Index'); # lazy default does not run
$page->render(contacts => $contacts, heading => undef); # explicit undef is supplied
An omitted $contacts raises a missing-required-argument error. Passing an unknown name, such as colour => 'blue', raises an unknown-argument error. An explicit undef is not absence: it suppresses the lazy default and renders as an empty value when used in an expression.
The application's display_heading helper increments heading_calls. That counter demonstrates the distinction: a default render increments it once, while a render with an explicit heading leaves it unchanged.
ESCAPING HTML SAFELY
The first contact is named <Ada>. Automatic escaping makes the visible result safe HTML:
<strong><Ada></strong>
The application enables auto_escape => 1. Therefore ordinary template expressions such as <%= $contact->{name} %> escape untrusted values. Use this setting for HTML output unless a value has already been made safe for its exact output context.
The partial and layout helpers return safe rendered output. Their composed HTML is not escaped a second time, so the final list item is still:
<li data-root="Contacts" data-parent="Contacts"><strong><Ada></strong> ada@example.test</li>
Use raw only for HTML that is already trusted and safe. Templates execute arbitrary Perl; sandbox_ns isolates unqualified symbols but is not a security boundary. Compile templates and accept template paths only from trusted sources.
PARTIALS
Each contact renders as one list item, including its independently escaped name:
<li data-root="Contacts" data-parent="Contacts"><strong>Grace</strong> grace@example.test</li>
The contacts/item.epl file is a partial because it renders an immediate, reusable piece of its caller's output. It has its own complete argument contract:
% args $contact, $root_title, $parent_title
<li data-root="<%= $root_title %>" data-parent="<%= $parent_title %>"><strong><%= $contact->{name} %></strong> <%= $contact->{email} %></li>
The page invokes it once for each record:
Fragment:
% for my $contact (@$contacts) {
%= partial 'contacts/item', contact => $contact, root_title => $title, parent_title => $title
% }
The explicit root_title and parent_title values make the partial's data dependencies visible. A partial does not receive lexical variables merely because its caller has them. Missing required names and unknown names are reported against the partial's own args declaration.
Rendered partial output is safe composition output. With auto_escape, the engine escapes <Ada> while the partial renders it, then preserves that safe result when the page inserts the whole partial.
LAYOUTS
The page declares its outer wrapper first, but its visible body appears inside that wrapper after rendering:
<body>
<section class="page">
...
</section>
</body>
This is the page declaration:
Fragment:
% layout 'layouts/application', title => $title
layout registers a deferred wrapper; it does not render the layout at that line. Once the page body is complete, the engine renders the registered layout around it. The application layout is:
% args $title
<!doctype html>
<html>
<head>
<title><%= $title %></title>
%= yield 'head'
</head>
<body>
%= yield
</body>
</html>
yield receives the page body. yield 'head' receives named content collected for the head section. Layout arguments are named arguments for the layout itself, independent of the page's args contract. Multiple layout declarations nest, with the first declaration outermost.
Like a partial, a rendered layout is safe composition output. Its complete rendered HTML is inserted once and is not re-escaped by the caller.
NAMED CONTENT
The Contacts page contributes this visible metadata to the layout's head:
<meta name="section" content="contacts">
It uses content_replace before writing the page body:
Fragment:
%= content_replace 'head', sub {
<meta name="section" content="contacts">
% }
content_for $name, sub { ... } appends captured output in render order. content_replace $name, sub { ... } discards earlier content for that name and stores its replacement. has_content $name reports whether the named content is nonempty, which lets a layout conditionally render an optional slot:
Fragment:
% if (has_content 'head') {
%= yield 'head'
% }
The application uses yield 'head' directly because it always supplies that contribution. Captured named content and the page body are safe rendered output, so layout composition continues the same once-only escaping rule as partials.
Here is the complete final output from examples/contacts/untyped/app.pl, exactly as the executable test asserts:
<!doctype html>
<html>
<head>
<title>Contacts</title>
<meta name="section" content="contacts">
</head>
<body>
<section class="page">
<main>
<h1>CONTACTS</h1>
<p class="badge">2 contacts</p>
<ul>
<li data-root="Contacts" data-parent="Contacts"><strong><Ada></strong> ada@example.test</li>
<li data-root="Contacts" data-parent="Contacts"><strong>Grace</strong> grace@example.test</li>
</ul>
</main>
</section>
</body>
</html>
APPLICATION HELPERS
The Contacts heading is transformed by an application helper, producing:
CONTACTS
Helpers receive the engine first, followed by the values passed from the template. Use this signature:
sub { my ($engine, @args) = @_; ... }
The application's helper closes over its counter so the lazy default can be observed:
Fragment:
display_heading => sub {
my ($engine, $value) = @_;
$self->{heading_calls}++;
return uc $value;
},
The page's $heading lazy default calls display_heading($title). A default render calls the helper once. $app->render(heading => 'Directory') does not call it again because a supplied argument overrides the lazy default.
Keep helpers focused on application policy and pass all input explicitly. They run as ordinary trusted Perl code in the application process, not in a separate template sandbox.
DIAGNOSING FAILURES
Read a diagnostic with its source excerpt
Give string templates a source name so compilation and runtime failures identify both the source and original template line. The following incomplete fragment deliberately refers to an undeclared variable:
Fragment: compile diagnostic
my $compile_error = eval {
Template::EmbeddedPerl->from_string(
"first\n<%= \$missing %>\n",
source => 'tutorial-compile.epl',
);
'';
} || $@;
print $compile_error;
The current diagnostic is:
Global symbol "$missing" requires explicit package name (did you forget to declare "my $missing"?) at tutorial-compile.epl line 2
1: first
2: <%= $missing %>
For a runtime failure, the engine also records the top-level render frame:
Fragment: runtime diagnostic
my $runtime_error = eval {
Template::EmbeddedPerl->from_string(
"first\n<% die 'tutorial runtime' %>\n",
source => 'tutorial-runtime.epl',
)->render;
'';
} || $@;
print $runtime_error;
tutorial runtime at tutorial-runtime.epl line 2
1: first
2: <% die 'tutorial runtime' %>
Render stack:
root tutorial-runtime.epl (tutorial-runtime.epl)
Warnings preserve their source and line when captured by the caller. Unlike a compile or runtime failure, a warning itself does not include the source excerpt, so inspect the named template line beside the message:
Fragment: warning diagnostic
my @warnings;
{
local $SIG{__WARN__} = sub { push @warnings, @_ };
Template::EmbeddedPerl->from_string(
"first\n<% warn 'tutorial warning' %>\n",
source => 'tutorial-warning.epl',
)->render;
}
print join '', @warnings;
tutorial warning at tutorial-warning.epl line 2.
1: first
2: <% warn 'tutorial warning' %>
Diagnose a file-backed template
Files give the same diagnostics with their resolved absolute path, which is usually easier to follow than an anonymous string. Create this scratch file:
Complete scratch file: /tmp/contacts-diagnostics/pages/broken.epl
before
<% die 'file-backed runtime' %>
Render it through an engine whose directory is /tmp/contacts-diagnostics:
Fragment: file-backed diagnostic
my $engine = Template::EmbeddedPerl->new(
directories => ['/tmp/contacts-diagnostics'],
smart_lines => 1,
);
my $error = eval { $engine->from_file('pages/broken')->render; '' } || $@;
print $error;
file-backed runtime at /tmp/contacts-diagnostics/pages/broken.epl line 2
1: before
2: <% die 'file-backed runtime' %>
Render stack:
root pages/broken (/tmp/contacts-diagnostics/pages/broken.epl)
Keep the source name or file path in the diagnostic. It identifies the broken template even when the render started in a different application module.
Read a composed render stack
When composition crosses files, read the render stack from the root toward the failure. These two complete scratch files make the relationship explicit:
Complete scratch file: /tmp/contacts-diagnostics/pages/contacts.epl
before
%= partial 'partials/broken'
after
Complete scratch file: /tmp/contacts-diagnostics/partials/broken.epl
child before
<% die 'composed runtime' %>
Render the root page with the same directory configuration:
Fragment: composed render diagnostic
my $error = eval { $engine->from_file('pages/contacts')->render; '' } || $@;
print $error;
composed runtime at /tmp/contacts-diagnostics/partials/broken.epl line 2
1: child before
2: <% die 'composed runtime' %>
Render stack:
root pages/contacts (/tmp/contacts-diagnostics/pages/contacts.epl)
partial partials/broken (/tmp/contacts-diagnostics/partials/broken.epl)
Repair the local partial first, then use the preceding root and partial frames to find the composition path that reached it. preamble can add a required import before the generated template subroutine without changing reported template line numbers. The Contacts application needs no preamble; add one only for a deliberate import and keep template syntax compatible with the Perl version your application supports.
REUSE AND PRODUCTION CONFIGURATION
For a persistent application, compile a template once and render it many times:
my $compiled = $engine->from_file('pages/contacts');
my $html = $compiled->render(contacts => $app->contacts);
Keep the engine and compiled templates alive for the lifetime of the process. Retaining $compiled already avoids recompilation when it is rendered again. Enable use_cache => 1 when code repeatedly asks the engine to look up or compile the same template with from_file or from_string; it can reuse the in-memory compiled code for those repeated engine calls. It helps in persistent processes such as PSGI applications; a short-lived command gains little from an in-memory cache.
Configure shared and application templates in deliberate order:
Fragment: production configuration
my $engine = Template::EmbeddedPerl->new(
directories => [
'/srv/contacts/templates',
'/srv/shared/templates',
],
smart_lines => 1,
auto_escape => 1,
use_cache => 1,
);
The first matching file wins. This application does not need a preamble. Add one only for an explicit import, and use syntax compatible with the Perl version your application supports rather than treating a version preamble as a template requirement. Do not let request data choose a template path, and do not compile templates supplied by untrusted users. Templates can execute arbitrary Perl, so path ordering and trusted sources are production security boundaries rather than convenience settings.
CHOOSING THE NEXT ABSTRACTION
For a small page, plain templates with named arguments keep data flow direct. Extract a partial when a repeatable fragment needs its own explicit data contract. Add a layout and named content when several pages share a wrapper or need well-defined slots such as head.
Keep using the untyped approach while data is simple and callers can pass the needed values directly. Consider typed views only when application-specific object construction, dependency injection, or reusable view relationships make those boundaries clearer than named arguments. The untyped Contacts application remains a complete and useful production shape for many programs.
SEE ALSO
Template::EmbeddedPerl, Template::EmbeddedPerl::Cookbook, and Template::EmbeddedPerl::Cookbook::TypedViews.