Nette Documentation Preview

syntax
Latte For Developers
********************


How to Render a Template
========================

Just run this code:

```php
$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:

```php
$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 |type-system]
and way for registration of [filters|#Custom Filters] and [#functions]. The example uses the capabilities of PHP 8:

```php
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`:

```php
$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:

```php
$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:

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

We use it in a template like this:

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

The second way to define a custom filter is the template class. You create a method and give it the `TemplateFilter` attribute:

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

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

If you are using PHP older than 8.0 and Latte 2.x, you can use an annotation instead of an attribute:

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


Filter Loaders .{data-version:2.10}
-----------------------------------

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

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

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

```php
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:

```php
$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:

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

.[note]
The filter must ensure proper data escaping.


Block filters
-------------

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

```php
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`:

```php
$info->contentType = Engine::CONTENT_TEXT;
```

The following types and their contexts are distinguished:

```php
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:

```php
$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:

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

The second way to define a custom function is the template class. You create a method and give it the `TemplateFunction` attribute:

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

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

If you are using PHP older than 8.0 and Latte 2.x, you can use an annotation instead of an attribute:

```php
/** @function */
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).

```php
$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:

```php
$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 |api:Latte\MacroNode] object which represents the current node, the second parameter is [PhpWriter |api:Latte\PhpWriter] object which helps with generating the output code.

```php
$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}`|tags#try] and in the [sandbox] are passed to it.

```php
$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:

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

{{composer: latte/latte}}

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 custom filter is the template class. You create a method and give it the TemplateFilter attribute:

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

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

If you are using PHP older than 8.0 and Latte 2.x, you can use an annotation instead of an attribute:

/** @filter */
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 custom function is the template class. You create a method and give it the TemplateFunction attribute:

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

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

If you are using PHP older than 8.0 and Latte 2.x, you can use an annotation instead of an attribute:

/** @function */
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);