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();
}

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.

version: 3.0 2.x