Templating
Templates are a tremendous feature of Nette Framework which eases your work and ensures the output is protected against vulnerabilities, such as XSS.
- you will learn how to use the macros
- blocks and inheritance
- using and creating the helpers
- creating user-defined macros
- avoiding security vulnerabilities
Although PHP is originally a templating language it isn't suited for writing templates. Let's have a look at an example of PHP
template that prints an array $items
as a list:
<?php if ($items): ?>
<?php $counter = 1 ?>
<ul>
<?php foreach ($items as $item): ?>
<li id="item-<?php echo $counter++ ?>"><?php
echo htmlSpecialChars(mb_convert_case($item, MB_CASE_TITLE)) ?>
</li>
<?php endforeach ?>
</ul>
<?php endif?>
The code is rather confusing and also we must not forget to call htmlSpecialChars
function. That's why there are
so many different template engines for PHP. One of the best template engines is part of Nette Framework and it is called
Latte. You'll love it!
Latte
The same template is written easily in Latte:
<ul n:if="$items">
{foreach $items as $item}
<li id="item-{$iterator->counter}">{$item|capitalize}</li>
{/foreach}
</ul>
As you can see there are two types of macros:
- macro in braces, for example
{foreach …}
- n:macro, for example
n:if="…"
Macros
You can find detailed description of all the default macros on the extra page. Furthermore, you can make your own user-defined macros.
Each pair macro, such as {if} … {/if}
, operating upon single HTML element can be written in n:macro notation. So, it is possible to write the {foreach}
macro in the same manner:
<ul n:if="$items">
<li n:foreach="$items as $item">{$item|capitalize}</li>
</ul>
With n:macros you can do much more interesting tricks as you will see in a moment.
{$item|capitalize}
macro which prints the $item
variable contains so called helper, in this case the capitalize
helper which makes the first letter of each word
uppercase.
Very important feature of Latte is that it escapes variables by default. Escaping is needed when printing a variable because we have to convert all the characters which have a special meaning in HTML to other sequences. In case we forget it can lead to a serious security hole called Cross Site Scripting (XSS).
Because of different escaping functions that are needed in different documents and different parts of a page, Latte features a unique technology of Context-Aware Escaping which recognizes the context in which the macro is placed and chooses the right escaping mode. You don't have to worry that your coder forgets about it causing you goose bumps because of a security hole. Which is great!
If the $item
variable stores an HTML code and you want to print it without any alteration you just
add modifier noescape
: {$item|noescape}
(since release 2.0.11) or put the exclamation mark right after
the opening brace: {!$item}
. Forgetting the exclamation mark won't cause any security holes in spirit of „less
code, more security“ principle.
You can still use PHP inside the macros normally, including comments as well. But Latte also extends the PHP syntax with three pleasant features:
- array can be written as
[1, 2, 3]
, which is the same asarray(1, 2, 3)
in PHP - we can omit quotes around the strings consisting of letters, numbers and dashes
- short condition notation
$a ? 'b'
which is the same as$a ? 'b' : null
in PHP
For example:
{foreach [a, b, c] as $id} ... {/foreach}
{$cond ? hello} // prints 'hello' if $cond equals true
Latte also has a {* comment macro *}
which doesn't get printed to the output.
n:macros
We showed that n:macros are supposed to be written directly into HTML tags as their special attributes. We also said that every
pair macro (e.g. {if} … {/if}
) can be written in n:macro notation. The macro then corresponds to the HTML element
in which it is written:
{var $items = ['I', '♥', 'Nette Framework']}
<p n:foreach="$items as $item">{$item}</p>
Prints:
<p>I</p>
<p>♥</p>
<p>Nette Framework</p>
By using inner-
prefix we can alter the behavior so that the macro applies only to the body of the element:
<div n:inner-foreach="$items as $item">
<p>{$item}</p>
<hr>
</div>
Prints:
<div>
<p>I</p>
<hr>
<p>♥</p>
<hr>
<p>Nette Framework</p>
<hr>
</div>
Or by using tag-
prefix the macro is applied on the HTML tags only:
<p><a href="{$url}" n:tag-if="$url">Title</a></p>
Depending on the value of $url
variable this will print:
// when $url is empty
<p>Title</p>
// when $url equals 'https://nette.org'
<p><a href="https://nette.org">Title</a></p>
However, n:macros are not only a shortcut for pair macros, there are some pure n:macros as well, for example n:href or the coder's best friend n:class macro.
Helpers in Latte
Latte allows calling helpers by using the pipe sign notation (preceding space is allowed):
<h1>{$heading|upper}</h1>
Helpers (or modifiers) can be chained, in that case they apply in order from left to right:
<h1>{$heading|lower|capitalize}</h1>
Parameters are put after the helper name separated by colon or comma:
<h1>{$heading|truncate:20,''}</h1>
See the summary of standard helpers and how to make user-defined helpers.
Performance
Latte is fast. It compiles the templates to native PHP code and stores them in cache on the disk. So they are as fast as if they would have been written in pure PHP.
The template is automatically recompiled each time we change the source file. While developing you just need to edit the templates in Latte and changes are visible in your browser instantly.
Debugging
With each error or typo you will be informed by the Debugger with all the luxury. The template source code is displayed and the red line marks the error showing error message as well. With just a single click you can open the template in your favorite editor and fix the error at once. Easy peasy!
If you are using an IDE with code stepping you can go through the generated PHP code of the template.
Usability
Latte syntax wasn't invented by engineers but came up from webdesigner's practical requests. We were looking for the friendliest syntax with which you can write even the most problematic constructions comfortably enough. You will be surprised how much help Latte can be.
You can find macros for advanced layout managing, for template inheritance, nested blocks and so on. Syntax comes from PHP itself so you don't have to learn anything new and you can leverage your know-how.
Context-Aware Escaping
Although the Cross Site Scripting (XSS) is one of the trivial ways of exploiting a web page it is the most common vulnerability but very serious. It can lead to identity theft and so on. The best defense is consistent escaping of printed data, ie. converting the characters which have a special meaning in the given context.
If the coder omits the escaping a security hole is made. That's why template engines implement automated escaping. The problem is that the web page has different contexts and each has different rules for escaping printed data. A security hole then shows up if the wrong escaping functions are used.
But Latte is sophisticated. It features unique technology of Context-Aware Escaping which recognizes the context in which the macro is placed and chooses the right escaping mode. What does that mean?
Latte doesn't need any manual work. All is done automatically, consistently and correctly. You don't have to worry about security holes.
Lets see how it works:
<p onclick="alert({$movie})">{$movie}</p>
<script>var movie = {$movie};</script>
If $movie
variable stores 'Amarcord & 8 1/2'
string it generates the following output. Notice
different escaping used in HTML and JavaScript and also in onclick
attribute:
<p onclick="alert("Amarcord & 8 1\/2")">Amarcord & 8 1/2</p>
<script>var movie = "Amarcord & 8 1\/2";</script>
Thanks to Context-Aware Escaping the template is simple and your application perfectly secured against Cross Site Scripting. You can use PHP variables natively inside the JavaScript!
A pretty output
Sticklers will enjoy the look of the HTML output which Latte generates. All tags are indented as they are supposed to. The code looks like it has been processed with some kind of HTML code beautifier :-)
User-defined macros
Latte provides API for making your own macros. It isn't difficult at all. Macros are added in sets (a set can consist of a single macro).
$latte = new Nette\Latte\Engine;
// lets create a set
$set = new Nette\Latte\Macros\MacroSet($latte->compiler);
// add new pair macro {try} ... {/try}
$set->addMacro(
'try', // macro name
'try {', // PHP code replacing the opening brace
'} catch (\Exception $e) {}' // code replacing the closing brace
);
If we omit the last parameter of addMacro()
method, we denote the macro is not paired.
PHP code in the second and third parameter can contain tags:
%node.word
– inserts the first macro argument%node.array
– inserts the macro arguments formated as a PHP array%node.args
– inserts the macro arguments formated as PHP code%escape(...)
– replaced by the current escape function%modify(...)
– replaced by a sequence of modifiers
For example:
$set->addMacro('if', 'if (%node.args):', 'endif');
If the macro logic is more complex we can use callbacks or lambda functions instead of strings. In the first parameter they will get MacroNode object which represents the current macro, the second parameter is PhpWriter object which helps with generating the output code.
$set->addMacro('if', function($node, $writer) {
return $writer->write('if (%node.args):');
}, 'endif');
Helpers
In templates we can use functions which change or format the data to a form we want. They are called helpers. See the summary of the default helpers.
Helper can be registered by any callback or lambda function:
$template->registerHelper('shortify', function ($s) {
return mb_substr($s, 0, 10); // shortens the text to 10 characters
});
In this case it would be better for the helper to get an extra parameter:
$template->registerHelper('shortify', function ($s, $len = 10) {
return mb_substr($s, 0, $len);
});
We call it in a template like this:
<p><?php echo $template->shortify($text, 100); ?></p>
Latte simplifies the notation – helpers are denoted by the pipe sign, they can be chained (they apply in order from left to right). Parameters are separated by colon or comma:
<p>{$text|shortify:100}</p>
Independent template usage
While using presenters we usually don't need to do anything but to write a template. Everything else does the framework for us. However, we can use the templates independently, for example in existing application which doesn't use Nette.
Template classes are in Nette\Templating
namespace and the core class is Nette\Templating\Template. Here is an example of how to work
with it:
use Nette\Templating\Template;
$template = new Template;
// passing parameters to the template:
$template->firstName = 'John';
$template->lastName = 'Doe';
$template->setSource('
<em><?php echo htmlspecialchars($firstName) ?></em>
<strong><?php echo htmlspecialchars($lastName) ?></strong>
');
$template->render();
render()
method renders the template, ie. the PHP script we passed as a parameter to getSource()
method. The parameters will be available in local variables $firstName
and $lastName
. Additionally, the
template object is stored in the $template
variable.
If the template is stored as a file it is better to use FileTemplate class:
use Nette\Templating\FileTemplate;
$template = new FileTemplate('template.latte'); // template file
// $template->setFile('template.latte'); // file path can be specified also later
$template->firstName = 'John';
$template->lastName = 'Doe';
$template->render(); // renders the template
We need to register the helpers if we want to use them in the template. Default helpers are registered like this:
$template->registerHelperLoader('Nette\Templating\Helpers::loader');
A template can be preprocessed with one or more filters. Filter is a function which gets the content of the template as a parameter and returns altered template. Any callback or lambda function can be registered as a filter.
// filter which replaces 'apple' with 'pizza'
$template->registerFilter(function ($s) {
return str_replace('apple', 'pizza', $s);
});
For registering the filter in presenters and components it is best to override templatePrepareFilters
method.
The most complex filter is Latte:
$template->registerFilter(new Nette\Latte\Engine);
We get native PHP template after applying all the registered filters. It is saved in the cache so the filters are applied just
once. The cache is invalidated automatically once the source code is changed and it is saved in special format which allows the
files to be loaded with include
. That's why you have to specify a storage where the cache files will be saved:
$template->setCacheStorage(new Nette\Caching\Storages\PhpFileStorage('temp'));
You can register the Latte filter in the onPrepareFilters
event handler to avoid creating and registering the
filter every time the filtered templates are saved in the cache:
$template->onPrepareFilters[] = function($template) {
$template->registerFilter(new Nette\Latte\Engine);
};
Now you know everything about templates.