Practices for Developers
Installation
The best way how to install Latte is to use a Composer:
composer require latte/latte
Supported PHP versions (applies to the latest patch Latte versions):
version | compatible with PHP |
---|---|
Latte 3.0 | PHP 8.0 – 8.2 |
How to Render a Template
How to render a template? Just use this simple code:
$latte = new Latte\Engine;
// cache directory
$latte->setTempDirectory('/path/to/tempdir');
$params = [ /* template variables */ ];
// or $params = new TemplateParameters(/* ... */);
// render to output
$latte->render('template.latte', $params);
// or render to variable
$output = $latte->renderToString('template.latte', $params);
Parameters can be arrays or even better object, which will provide type checking and suggestion in the editor.
You can also find usage examples in the repository Latte examples.
Performance and Caching
Latte templates are extremely fast, because Latte compiles them directly into PHP code and caches them on disk. Thus, they have no extra overhead compared to templates written in pure PHP.
The cache is automatically regenerated every time you change the source file. So you can conveniently edit your Latte templates during development and see the changes immediately in the browser. You can disable this feature in a production environment and save a little performance:
$latte->setAutoRefresh(false);
When deployed on a production server, the initial cache generation, especially for larger applications, can understandably take a while. Latte has built-in prevention against cache stampede. This is a situation where server receives a large number of concurrent requests and because Latte's cache does not yet exist, they would all generate it at the same time. Which spikes CPU. Latte is smart, and when there are multiple concurrent requests, only the first thread generates the cache, the others wait and then use it.
Parameters as a Class
Better than passing variables to the template as arrays is to create a class. You get type-safe notation, nice suggestion in IDE and a way to register filters and functions.
class MailTemplateParameters
{
public function __construct(
public string $lang,
public Address $address,
public string $subject,
public array $items,
public ?float $price = null,
) {}
}
$latte->render('mail.latte', new MailTemplateParameters(
lang: $this->lang,
subject: $title,
price: $this->getPrice(),
items: [],
address: $userAddress,
));
Disabling Auto-Escaping of Variable
If the variable contains an HTML string, you can mark it so that Latte does not automatically (and therefore double) escape it.
This avoids the need to specify |noescape
in the template.
The easiest way is to wrap the string in a Latte\Runtime\Html
object:
$params = [
'articleBody' => new Latte\Runtime\Html($article->htmlBody),
];
Latte also does not escape all objects that implement the Latte\HtmlStringable
interface. So you can create your
own class whose __toString()
method will return HTML code that will not be escaped automatically:
class Emphasis extends Latte\HtmlStringable
{
public function __construct(
private string $str,
) {
}
public function __toString(): string
{
return '<em>' . htmlspecialchars($this->str) . '</em>';
}
}
$params = [
'foo' => new Emphasis('hello'),
];
The __toString
method must return correct HTML and provide parameter escaping, otherwise an XSS
vulnerability may occur!
How to Extend Latte with Filters, Tags, etc.
How to add a custom filter, function, tag, etc. to Latte? Find out in the chapter extending Latte. If you want to reuse your changes in different projects or if you want to share them with others, you should then create an extension.
Any Code in Template {php ...}
Only PHP expressions can be written inside the {do}
tag, so
you can't, for example, insert constructs like if ... else
or semicolon-terminated statements.
However, you can register the RawPhpExtension
extension, which adds the {php ...}
tag. You can use
this to insert any PHP code. It is not subject to any sandbox mode rules, so use is the responsibility of the template author.
$latte->addExtension(new Latte\Essential\RawPhpExtension);
Checking Generated Code
Latte compiles templates into PHP code. Of course, it ensures that the generated code is syntactically valid. However, when using third-party extensions or RawPhpExtension, Latte cannot guarantee the correctness of the generated file. Also, in PHP, you can write code that is syntactically correct but is forbidden (for example, assigning a value to the $this variable) and causes a PHP Compile Error. If you write such an operation in a template, it will also be included in the generated PHP code. Since there are over two hundred different forbidden operations in PHP, Latte does not aim to detect them. PHP itself will flag them upon rendering, which usually isn't a problem.
However, there are situations where you want to know during the template compilation that it contains no PHP Compile Errors. Especially when templates can be edited by users, or you use Sandbox. In such a case, have the templates checked during compilation. You can activate this functionality using the Engine::enablePhpLint() method. Since it needs to call the PHP binary for the check, pass its path as a parameter:
$latte = new Latte\Engine;
$latte->enablePhpLinter('/path/to/php');
try {
$latte->compile('home.latte');
} catch (Latte\CompileException $e) {
// catches Latte errors and also Compile Error in PHP
echo 'Error: ' . $e->getMessage();
}
Locale
Latte allows you to set the locale, which affects the formatting of numbers, dates, and sorting. It is set using the
setLocale()
method. The locale identifier follows the IETF language tag standard, which uses the PHP
intl
extension. It consists of a language code and possibly a country code, for example, en_US
for
English in the United States, de_DE
for German in Germany, etc.
$latte = new Latte\Engine;
$latte->setLocale('cs');
The locale setting affects the filters localDate, sort, number, and bytes.
Requires the PHP intl
extension. The setting in Latte does not affect the global locale setting
in PHP.
Strict Mode
In strict parsing mode, Latte checks for missing closing HTML tags and also disables the use of the $this
variable. To turn it on:
$latte = new Latte\Engine;
$latte->setStrictParsing();
To generate templates with the declare(strict_types=1)
header, do the following:
$latte = new Latte\Engine;
$latte->setStrictTypes();
Translation in Templates
Use the TranslatorExtension
extension to add {_...}
, {translate}
and filter translate
to the template. They are used to translate
values or parts of the template into other languages. The parameter is the method (PHP callable) that performs the
translation:
class MyTranslator
{
public function __construct(private string $lang)
{}
public function translate(string $original): string
{
// create $translated from $original according to $this->lang
return $translated;
}
}
$translator = new MyTranslator($lang);
$extension = new Latte\Essential\TranslatorExtension(
$translator->translate(...), // [$translator, 'translate'] in PHP 8.0
);
$latte->addExtension($extension);
The translator is called at runtime when the template is rendered. However, Latte can translate all static texts during template compilation. This saves performance because each string is translated only once and the resulting translation is written to the compiled file. This creates multiple compiled versions of the template in the cache directory, one for each language. To do this, you only need to specify the language as the second parameter:
$extension = new Latte\Essential\TranslatorExtension(
$translator->translate(...),
$lang,
);
By static text we mean, for example, {_'hello'}
or {translate}hello{/translate}
. Non-static text,
such as {_$foo}
, will continue to be translated at runtime.
The template can also pass additional parameters to the translator via {_$original, foo: bar}
or
{translate foo: bar}
, which it receives as the $params
array:
public function translate(string $original, ...$params): string
{
// $params['foo'] === 'bar'
}
Debugging and Tracy
Latte tries to make the development as pleasant as possible. For debugging purposes, there are three tags {dump}
, {debugbreak}
and {trace}
.
You'll get the most comfort if you install the great debugging tool Tracy and activate the Latte plugin:
// enables Tracy
Tracy\Debugger::enable();
$latte = new Latte\Engine;
// activates Tracy's extension
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
You will now see all errors in a neat red screen, including errors in templates with row and column highlighting (video). At the same time, in the bottom right corner in the so-called Tracy Bar, a tab for Latte appears, where you can clearly see all rendered templates and their relationships (including the possibility to click into the template or compiled code), as well as variables:
Since Latte compiles templates into readable PHP code, you can conveniently step through them in your IDE.
Linter: Validating the Template Syntax
The Linter tool will help you go through all templates and check for syntax errors. It is launched from the console:
vendor/bin/latte-lint <path>
Use the --strict
parameter to activate strict mode.
If you use custom tags, also create your customized Linter, e.g. custom-latte-lint
:
#!/usr/bin/env php
<?php
// enter the actual path to the autoload.php file
require __DIR__ . '/vendor/autoload.php';
$path = $argv[1] ?? '.';
$linter = new Latte\Tools\Linter;
$latte = $linter->getEngine();
// add your individual extensions here
$latte->addExtension(/* ... */);
$ok = $linter->scanDirectory($path);
exit($ok ? 0 : 1);
Alternatively, you can pass your own Latte\Engine
object to the Linter:
$latte = new Latte\Engine;
// here we configure the $latte object
$linter = new Latte\Tools\Linter(engine: $latte);
Loading Templates from a String
Need to load templates from strings instead of files, perhaps for testing purposes? StringLoader will help you:
$latte->setLoader(new Latte\Loaders\StringLoader([
'main.file' => '{include other.file}',
'other.file' => '{if true} {$var} {/if}',
]));
$latte->render('main.file', $params);
Exception Handler
You can define your own handler for expected exceptions. Exceptions raised inside {try}
and in the sandbox are passed to it.
$loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) {
$logger->log($e);
};
$latte = new Latte\Engine;
$latte->setExceptionHandler($loggingHandler);
Automatic Layout Lookup
Using the tag {layout}
, the
template determines its parent template. It's also possible to have the layout searched automatically, which will simplify
writing templates since they won't need to include the {layout}
tag.
This is achieved as follows:
$finder = function (Latte\Runtime\Template $template) {
if (!$template->getReferenceType()) {
// it returns the path to the parent template file
return 'automatic.layout.latte';
}
};
$latte = new Latte\Engine;
$latte->addProvider('coreParentFinder', $finder);
If the template should not have a layout, it will indicate this with the {layout none}
tag.