Latte For Developers

How to Render a Template

Just run this code:

$latte = new Latte\Engine;

$latte->setTempDirectory('/path/to/tempdir');

$params = [
	'items' => ['one', 'two', 'three'],
];

// render to output
$latte->render('template.latte', $params);
// or render to string
$html = $latte->renderToString('template.latte', $params);

Latte automatically regenerates the cache every time you change the template, which can be turned off in the production environment to save a little performance:

$latte->setAutoRefresh(false);

Typesafe parameters

You can also use an object instead of the $params array, which has some advantages. You get typesafe notation, autosuggestion in IDE and way for registration of filters and functions. The example uses the capabilities of PHP 8:

class MailTemplate
{
	public function __construct(
		public string $lang = 'cs',
		public Address $address,
		public string $subject,
		public array $items,
		public ?float $price = null,
	) {}
}

$latte->render('mail.latte', new MailTemplate(
	lang: $this->lang,
	subject: $title,
	price: $this->getPrice(),
	items: [],
	address: $userAddress,
));

Template from String

You can load template from strings using Latte\Loaders\StringLoader:

$latte->setLoader(new Latte\Loaders\StringLoader([
	'main.file' => '{include other.file}',
	'other.file' => '{if true} {$var} {/if}',
]));

$latte->render('main.file', $params);

Custom Filters

Custom filters can be registered this way:

$latte = new Latte\Engine;
$latte->addFilter('shortify', function (string $s): string {
	return mb_substr($s, 0, 10); // shortens the text to 10 characters
});

In this case it would be better for the filter to get an additional parameter:

$latte->addFilter('shortify', function (string $s, int $len = 10): string {
	return mb_substr($s, 0, $len);
});

We use it in a template like this:

<p>{$text|shortify}</p>
<p>{$text|shortify:100}</p>

The second way to define a filter is a template class. It is important to specify the annotation @filter:

class MyTemplate
{
	/** @filter */
	public function shortify(string $s, int $len = 10): string
	{
		return mb_substr($s, 0, $len);
	}
}

$params = new MyTemplate;
...
$latte->render('template.latte', $params);

In PHP 8.0, you can use the so-called attribute instead of the annotation:

#[Latte\Attributes\TemplateFilter]
public function shortify(string $s, int $len = 10): string
{
	return mb_substr($s, 0, $len);
}

Filter Loaders

Manual registration of multiple filter can be replaced with a single filter loader:

$latte->addFilterLoader([new Filters, 'loader']);

Method loader() gets name of filter and returns callable:

class Filters
{
	public function loader(string $filter): ?callable
	{
		if (method_exists($this, $filter)) {
			return [$this, $filter];
		}
		return null;
	}

	public function shortify($s, $len = 10)
	{
		return mb_substr($s, 0, $len);
	}

	...
}

Universal filter

Before Latte v2.10 this way was used instead of the filter loader:

$latte->addFilter(null, [new Filters, 'loader']);

class Filters
{
	public function loader(string $filter, ...$args)
	{
		if (method_exists($this, $filter)) {
			return [$this, $filter](...$args);
		}
	}

	...
}

Escaping

The string that the filter receives at the input and returns at the output is plain UTF‑8 by default. If the filter returns HTML, it wraps the string in an object Latte\Runtime\Html to avoid unwanted automatic escaping in the template:

$latte->addFilter('money', function (int $num): string {
	return new Latte\Runtime\Html("<i>$num €</i");
});

The filter must ensure proper data escaping.

Block filters

Filters that are applied to blocks (eg anonymous blocks) receive information about the content type and context in the object Latte\Runtime\FilterInfo, which is passed as the first parameter. The filter should first verify that the content type supports:

use Latte\Engine;
use Latte\Runtime\FilterInfo;

$latte->addFilter('tidyHtml', function (FilterInfo $info, string $s): string {
	// verifies that there is HTML text in $s
	if ($info->contentType !== Engine::CONTENT_HTML) {
		throw new Exception('Filter |tidyHtml used in incompatible content type.');
	}
	...
});

For those filters, wrapping in the object Latte\Runtime\Html is not used, the type information is returned in the object $info. I.e. if it returns data in another type, it changes the value in $info:

$info->contentType = Engine::CONTENT_TEXT;

The following types and their contexts are distinguished:

use Latte\Engine;
use Latte\Compiler;

Engine::CONTENT_TEXT
Engine::CONTENT_HTML . Compiler::CONTEXT_HTML_TEXT
Engine::CONTENT_HTML . Compiler::CONTEXT_HTML_TAG
Engine::CONTENT_HTML . Compiler::CONTEXT_HTML_ATTRIBUTE
Engine::CONTENT_HTML . Compiler::CONTEXT_HTML_COMMENT
Engine::CONTENT_HTML . Compiler::CONTEXT_HTML_CSS
Engine::CONTENT_HTML . Compiler::CONTEXT_HTML_JS
Engine::CONTENT_XHTML
Engine::CONTENT_XML
Engine::CONTENT_JS
Engine::CONTENT_CSS
Engine::CONTENT_ICAL

If the block filter is used in the usual way (ie {$foo|tidyHtml}), the $info->contentType contains a null.

Functions

In Latte you can use all PHP functions and at the same time define your own:

$latte = new Latte\Engine;
$latte->addFunction('random', function (...$args) {
	return $args[array_rand($args)];
});

The usage is then the same as when calling the PHP function:

{random(apple, orange, lemon)} // prints for example: apple

The second way to define a function is a template class. It is important to specify the annotation @function:

class MyTemplate
{
	/** @function */
	public function random(...$args)
	{
		return $args[array_rand($args)];
	}
}

$params = new MyTemplate;
...
$latte->render('template.latte', $params);

In PHP 8.0, you can use the so-called attribute instead of the annotation:

#[Latte\Attributes\TemplateFunction]
public function random(...$args)
{
	return $args[array_rand($args)];
}

User-defined Tags

Latte provides API for making your own tags. It isn't difficult at all. Tags are added in sets (a set can consist of a single tag).

$latte = new Latte\Engine;

// lets create a set
$set = new Latte\Macros\MacroSet($latte->getCompiler());

// add new pair tag {try} ... {/try}
$set->addMacro(
	'try', // tag 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 tag is not paired.

PHP code in the second and third parameter can contain tags:

  • %node.word – inserts the first tag argument
  • %node.array – inserts the tag arguments formatted as a PHP array
  • %node.args – inserts the tag arguments formatted as PHP code
  • %node.line – inserts a comment with the line number in the template
  • %escape(...) – replaced by the current escape function
  • %modify(...) – replaced by a sequence of filters

For example:

$set->addMacro('if', 'if (%node.args):', 'endif');

If the tag 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 node, 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');

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

Layout Lookup

You can use the API to choose which layout template to use when child template do not contain the {layout} tag. This will simplify the writing of templates or allow automatic layout look up.

This is achieved in the following way:

$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);