Criando filtros personalizados

Filtros são ferramentas poderosas para formatar e modificar dados diretamente nos templates Latte. Eles oferecem uma sintaxe limpa usando o símbolo de pipe (|) para transformar variáveis ou resultados de expressões no formato de saída desejado.

O que são filtros?

Filtros no Latte são essencialmente funções PHP projetadas especificamente para transformar um valor de entrada em um valor de saída. Eles são aplicados usando a notação de pipe (|) dentro das expressões do template ({...}).

Conveniência: Filtros permitem encapsular tarefas comuns de formatação (como formatação de datas, alteração de maiúsculas/minúsculas, truncagem) ou manipulação de dados em unidades reutilizáveis. Em vez de repetir código PHP complexo nos seus templates, você pode simplesmente aplicar um filtro:

{* Em vez de PHP complexo para truncar: *}
{$article->text|truncate:100}

{* Em vez de código para formatar datas: *}
{$event->startTime|date:'Y-m-d H:i'}

{* Aplicando múltiplas transformações: *}
{$product->name|lower|capitalize}

Legibilidade: Usar filtros torna os templates mais limpos e focados na apresentação, pois a lógica de transformação é movida para a definição do filtro.

Sensibilidade ao contexto: Uma vantagem chave dos filtros no Latte é sua capacidade de serem sensíveis ao contexto. Isso significa que o filtro pode reconhecer o tipo de conteúdo com o qual está trabalhando (HTML, JavaScript, texto simples, etc.) e aplicar a lógica ou o escaping correspondente, o que é crucial para a segurança e a correção, especialmente ao gerar HTML.

Integração com a lógica da aplicação: Assim como as funções personalizadas, o PHP callable por trás de um filtro pode ser um closure, um método estático ou um método de instância. Isso permite que os filtros acessem serviços ou dados da aplicação, se necessário, embora seu propósito principal permaneça a transformação do valor de entrada.

O Latte fornece por padrão um rico conjunto de filtros padrão. Filtros personalizados permitem estender este conjunto com formatações e transformações específicas do seu projeto.

Se você precisa executar lógica baseada em múltiplas entradas ou não tem um valor primário para transformar, provavelmente é mais adequado usar uma função personalizada. Se você precisa gerar marcação complexa ou controlar o fluxo do template, considere uma tag personalizada.

Criando e registrando filtros

Existem várias maneiras de definir e registrar filtros personalizados no Latte.

Registro direto via addFilter()

A maneira mais simples de adicionar um filtro é usar o método addFilter() diretamente no objeto Latte\Engine. Você especifica o nome do filtro (como será usado no template) e o PHP callable correspondente.

$latte = new Latte\Engine;

// Filtro simples sem argumentos
$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.');

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

// Filtro processando arrays
$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers));

Uso no template:

{$name|initial}                 {* Imprime 'J.' se $name for 'John' *}
{$description|shortify}         {* Usa o comprimento padrão 10 *}
{$description|shortify:50}      {* Usa o comprimento 50 *}
{$prices|sum}                   {* Imprime a soma dos itens no array $prices *}

Passagem de argumentos:

O valor à esquerda do pipe (|) é sempre passado como o primeiro argumento para a função do filtro. Quaisquer parâmetros listados após os dois pontos (:) no template são passados como os argumentos seguintes.

{$text|shortify:30}
// Chama a função PHP shortify($text, 30)

Registro via extensão

Para melhor organização, especialmente ao criar conjuntos reutilizáveis de filtros ou compartilhá-los como pacotes, a maneira recomendada é registrá-los dentro de uma extensão Latte:

namespace App\Latte;

use Latte\Extension;

class MyLatteExtension extends Extension
{
	public function getFilters(): array
	{
		return [
			'initial' => $this->initial(...),
			'shortify' => $this->shortify(...),
		];
	}

	public function initial(string $s): string
	{
		return mb_substr($s, 0, 1) . '.';
	}

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

// Registro
$latte = new Latte\Engine;
$latte->addExtension(new App\Latte\MyLatteExtension);

Esta abordagem mantém a lógica do seu filtro encapsulada e o registro simples.

Usando o carregador de filtros

Latte permite registrar um carregador de filtros usando addFilterLoader(). É um único callable que o Latte solicitará para qualquer nome de filtro desconhecido durante a compilação. O carregador retorna o callable do filtro PHP ou null.

$latte = new Latte\Engine;

// O carregador pode criar/obter callables de filtro dinamicamente
$latte->addFilterLoader(function (string $name): ?callable {
	if ($name === 'myLazyFilter') {
		// Imagine aqui uma inicialização custosa...
		$service = get_some_expensive_service();
		return fn($value) => $service->process($value);
	}
	return null;
});

Este método foi projetado principalmente para carregamento lento de filtros com inicialização muito custosa. No entanto, práticas modernas de injeção de dependência geralmente lidam com serviços lentos de forma mais eficiente.

Carregadores de filtros adicionam complexidade e geralmente não são recomendados em favor do registro direto usando addFilter() ou dentro de uma extensão usando getFilters(). Use carregadores apenas se tiver uma razão séria e específica relacionada a problemas de desempenho na inicialização de filtros que não podem ser resolvidos de outra forma.

Filtros usando uma classe com atributos

Outra maneira elegante de definir filtros é usar métodos em sua classe de parâmetros de template. Basta adicionar o atributo #[Latte\Attributes\TemplateFilter] ao método.

use Latte\Attributes\TemplateFilter;

class TemplateParameters
{
	public function __construct(
		public string $description,
		// outros parâmetros...
	) {}

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

// Passando o objeto para o template
$params = new TemplateParameters(description: '...');
$latte->render('template.latte', $params);

Latte reconhecerá e registrará automaticamente os métodos marcados com este atributo quando o objeto TemplateParameters for passado para o template. O nome do filtro no template será o mesmo que o nome do método (shortify neste caso).

{* Usando o filtro definido na classe de parâmetros *}
{$description|shortify:50}

Filtros contextuais

Às vezes, um filtro precisa de mais informações do que apenas o valor de entrada. Ele pode precisar saber o tipo de conteúdo da string com a qual está trabalhando (por exemplo, HTML, JavaScript, texto simples) ou até mesmo modificá-lo. Esta é a situação para filtros contextuais.

Um filtro contextual é definido da mesma forma que um filtro comum, mas seu primeiro parâmetro deve ser tipado como Latte\Runtime\FilterInfo. O Latte reconhece automaticamente esta assinatura e, ao chamar o filtro, passa o objeto FilterInfo. Os parâmetros seguintes recebem os argumentos do filtro como de costume.

use Latte\Runtime\FilterInfo;
use Latte\ContentType;

$latte->addFilter('money', function (FilterInfo $info, float $amount): string {
	// 1. Verifique o tipo de conteúdo de entrada (opcional, mas recomendado)
	//    Permita null (entrada variável) ou texto simples. Rejeite se aplicado a HTML, etc.
	if (!in_array($info->contentType, [null, ContentType::Text], true)) {
		$actualType = $info->contentType ?? 'mixed';
		throw new \RuntimeException(
			"Filtro |money usado em tipo de conteúdo incompatível $actualType. Esperado texto ou null."
		);
	}

	// 2. Realize a transformação
	$formatted = number_format($amount, 2, '.', ',') . ' EUR';
	$htmlOutput = '<i>' . htmlspecialchars($formatted) . '</i>'; // Garanta o escaping correto!

	// 3. Declare o tipo de conteúdo de saída
	$info->contentType = ContentType::Html;

	// 4. Retorne o resultado
	return $htmlOutput;
});

$info->contentType é uma constante de string de Latte\ContentType (por exemplo, ContentType::Html, ContentType::Text, ContentType::JavaScript, etc.) ou null se o filtro for aplicado a uma variável ({$var|filter}). Você pode ler este valor para verificar o contexto de entrada e escrever nele para declarar o tipo do contexto de saída.

Ao definir o tipo de conteúdo para HTML, você informa ao Latte que a string retornada pelo seu filtro é HTML seguro. O Latte então não aplicará seu escaping automático padrão a este resultado. Isso é crucial se o seu filtro gera marcação HTML.

Se o seu filtro gera HTML, você é responsável por escapar corretamente quaisquer dados de entrada usados neste HTML (como no caso da chamada htmlspecialchars($formatted) acima). A omissão pode criar vulnerabilidades XSS. Se o seu filtro retorna apenas texto simples, você não precisa definir $info->contentType.

Filtros em blocos

Todos os filtros aplicados a blocos devem ser contextuais. Isso ocorre porque o conteúdo do bloco tem um tipo de conteúdo definido (geralmente HTML), do qual o filtro precisa estar ciente.

{block heading|money}1000{/block}
{* O filtro 'money' receberá '1000' como segundo argumento
   e $info->contentType será ContentType::Html *}

Filtros contextuais fornecem controle poderoso sobre como os dados são processados com base em seu contexto, permitem funcionalidades avançadas e garantem o comportamento correto do escaping, especialmente ao gerar conteúdo HTML.

versão: 3.0