Ampliação do Latte

O Latte é muito flexível e pode ser estendido de várias maneiras: você pode adicionar filtros personalizados, funções, tags, carregadores, etc. Nós lhe mostraremos como fazer isso.

Este capítulo descreve as diferentes maneiras de estender o Latte. Se você quiser reutilizar suas mudanças em diferentes projetos ou se quiser compartilhá-las com outros, você deve então criar a chamada extensão.

Quantas Estradas levam a Roma?

Como algumas das formas de estender o Latte podem ser misturadas, vamos primeiro tentar explicar as diferenças entre elas. Como exemplo, vamos tentar implementar um gerador Lorem ipsum, que é passado o número de palavras a serem geradas.

A principal construção da linguagem latte é a tag. Podemos implementar um gerador, ampliando o Latte com uma nova tag:

{lipsum 40}

A etiqueta vai funcionar muito bem. Entretanto, o gerador na forma de uma etiqueta pode não ser suficientemente flexível porque não pode ser usado em uma expressão. A propósito, na prática, raramente é necessário gerar tags; e isso é uma boa notícia, pois as tags são uma maneira mais complicada de se estender.

Ok, vamos tentar criar um filtro ao invés de uma etiqueta:

{=40|lipsum}

Mais uma vez, uma opção válida. Mas o filtro deve transformar o valor passado em algo mais. Aqui usamos o valor 40, que indica o número de palavras geradas, como argumento do filtro, e não como o valor que queremos transformar.

Portanto, vamos tentar usar a função:

{lipsum(40)}

É isso aí! Para este exemplo em particular, criar uma função é o ponto de extensão ideal a ser utilizado. Você pode chamá-la de qualquer lugar onde uma expressão é aceita, por exemplo:

{var $text = lipsum(40)}

Filtros

Criar um filtro registrando seu nome e qualquer PHP chamável, como por exemplo uma função:

$latte = new Latte\Engine;
$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // encurta o texto para 10 caracteres

Neste caso, seria melhor para o filtro obter um parâmetro adicional:

$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len));

Usamo-lo em um modelo como este:

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

Como você pode ver, a função recebe o lado esquerdo do filtro antes do tubo | as the first argument and the arguments passed to the filter after : como os próximos argumentos.

Naturalmente, a função que representa o filtro pode aceitar qualquer número de parâmetros, e parâmetros variáveis também são suportados.

Se o filtro retornar uma cadeia de caracteres em HTML, você poderá marcá-la para que o Latte não a escape automaticamente (e, portanto, duplamente). Isso evita a necessidade de especificar |noescape no modelo. A maneira mais fácil é envolver a cadeia de caracteres em um objeto Latte\Runtime\Html. A outra maneira é usar o Contextual Filters.

$latte->addFilter('money', fn(float $amount) => new Latte\Runtime\Html("<i>$amount EUR</i>"));

Nesse caso, o filtro deve garantir o escape correto dos dados.

Filtros que utilizam a classe

A segunda maneira de definir um filtro é usar classe. Criamos um método com o atributo TemplateFilter:

class TemplateParameters
{
	public function __construct(
		// parameters
	) {}

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

$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);

Carregador de filtros

Em vez de registrar filtros individuais, você pode criar um chamado carregador, que é uma função que é chamada com o nome do filtro como argumento e retorna seu PHP chamável, ou nulo.

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


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

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

	// ...
}

Filtros contextuais

Um filtro contextual é aquele que aceita um objeto Latte\Runtime\FilterInfo no primeiro parâmetro, seguido por outros parâmetros como no caso dos filtros clássicos. Ele é registrado da mesma forma, o próprio Latte reconhece que o filtro é contextual:

use Latte\Runtime\FilterInfo;

$latte->addFilter('foo', function (FilterInfo $info, string $str): string {
	// ...
});

Os filtros de contexto podem detectar e alterar o tipo de conteúdo que recebem na variável $info->contentType. Se o filtro for chamado classicamente sobre uma variável (por exemplo, {$var|foo}), o $info->contentType conterá nulo.

O filtro deve primeiro verificar se o tipo de conteúdo da cadeia de entrada é suportado. Ele também pode mudá-lo. Exemplo de um filtro que aceita texto (ou nulo) e retorna HTML:

use Latte\Runtime\FilterInfo;

$latte->addFilter('money', function (FilterInfo $info, float $amount): string {
	// primeiro verificamos se o tipo de conteúdo da entrada é texto
	if (!in_array($info->contentType, [null, ContentType::Text])) {
		throw new Exception("Filter |money used in incompatible content type $info->contentType.");
	}

	// mudar o tipo de conteúdo para HTML
	$info->contentType = ContentType::Html;
	return "<i>$amount EUR</i>";
});

Neste caso, o filtro deve garantir a fuga correta dos dados.

Todos os filtros que são utilizados sobre blocos (por exemplo, como {block|foo}...{/block}) deve ser contextual.

Funções

Por padrão, todas as funções do PHP nativo podem ser usadas em Latte, a menos que o sandbox o desabilite. Mas você também pode definir suas próprias funções. Elas podem sobrepor-se às funções nativas.

Crie uma função registrando seu nome e qualquer PHP que possa ser chamado:

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

O uso é então o mesmo que quando se chama a função PHP:

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

Funções usando a classe

A segunda maneira de definir uma função é usar a classe. Criamos um método com o atributo TemplateFunction:

class TemplateParameters
{
	public function __construct(
		// parameters
	) {}

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

$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);

Carregadeiras

Os carregadores são responsáveis por carregar modelos a partir de uma fonte, como um sistema de arquivo. Eles são definidos usando o método setLoader():

$latte->setLoader(new MyLoader);

Os carregadores embutidos são:

FileLoader

Carregador padrão. Carrega modelos do sistema de arquivos.

O acesso aos arquivos pode ser restringido através da configuração do diretório base:

$latte->setLoader(new Latte\Loaders\FileLoader($templateDir));
$latte->render('test.latte');

StringLoader

Carrega modelos a partir de cordas. Esta carregadeira é muito útil para testes unitários. Também pode ser usado para pequenos projetos onde pode fazer sentido armazenar todos os gabaritos em um único arquivo PHP.

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

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

Uso simplificado:

$template = '{if true} {$var} {/if}';
$latte->setLoader(new Latte\Loaders\StringLoader);
$latte->render($template);

Criando um Carregador Personalizado

Loader é uma classe que implementa a interface Latte\Loader.

Etiquetas

Uma das características mais interessantes do motor de modelagem é a capacidade de definir novas construções de linguagem usando tags. É também uma funcionalidade mais complexa e você precisa entender como o Latte funciona internamente.

Na maioria dos casos, no entanto, a etiqueta não é necessária:

  • se ela deve gerar alguma saída, use a função
  • se fosse para modificar alguma entrada e devolvê-la, usar filtro em seu lugar
  • se fosse para editar uma área de texto, embrulhe-a com um {block} e usar um filtro
  • se não era para produzir nada, mas apenas chamar uma função, chame-a com {do}

Se você ainda quiser criar uma etiqueta, ótimo! Todos os elementos essenciais podem ser encontrados em Criar uma Extensão.

Passes de Compilador

Os passes de compilador são funções que modificam os ASTs ou coletam informações neles. Em Latte, por exemplo, uma caixa de areia é implementada desta forma: atravessa todos os nós de um AST, encontra chamadas de funções e métodos, e as substitui por chamadas controladas.

Assim como as tags, esta é uma funcionalidade mais complexa e você precisa entender como o Latte funciona sob o capô. Todos os elementos essenciais podem ser encontrados no capítulo Criação de uma Extensão.

versão: 3.0