Creación de filtros personalizados

Los filtros son herramientas poderosas para formatear y modificar datos directamente en las plantillas Latte. Ofrecen una sintaxis limpia usando el símbolo de barra vertical (|) para transformar variables o resultados de expresiones al formato de salida deseado.

¿Qué son los filtros?

Los filtros en Latte son esencialmente funciones PHP diseñadas específicamente para transformar un valor de entrada en un valor de salida. Se aplican usando la notación de barra vertical (|) dentro de las expresiones de la plantilla ({...}).

Conveniencia: Los filtros le permiten encapsular tareas comunes de formato (como formatear fechas, cambiar mayúsculas/minúsculas, truncar) o manipulación de datos en unidades reutilizables. En lugar de repetir código PHP complejo en sus plantillas, simplemente puede aplicar un filtro:

{* En lugar de PHP complejo para truncar: *}
{$article->text|truncate:100}

{* En lugar de código para formatear fechas: *}
{$event->startTime|date:'Y-m-d H:i'}

{* Aplicación de múltiples transformaciones: *}
{$product->name|lower|capitalize}

Legibilidad: El uso de filtros hace que las plantillas sean más claras y más enfocadas en la presentación, ya que la lógica de transformación se traslada a la definición del filtro.

Sensibilidad al contexto: Una ventaja clave de los filtros en Latte es su capacidad para ser sensibles al contexto. Esto significa que un filtro puede reconocer el tipo de contenido con el que está trabajando (HTML, JavaScript, texto plano, etc.) y aplicar la lógica o el escape correspondientes, lo cual es crucial para la seguridad y la corrección, especialmente al generar HTML.

Integración con la lógica de la aplicación: Al igual que las funciones personalizadas, el PHP invocable detrás de un filtro puede ser un closure, un método estático o un método de instancia. Esto permite que los filtros accedan a servicios o datos de la aplicación si es necesario, aunque su propósito principal sigue siendo transformar el valor de entrada.

Latte proporciona por defecto un rico conjunto de filtros estándar. Los filtros personalizados le permiten ampliar este conjunto con formato y transformaciones específicas de su proyecto.

Si necesita realizar lógica basada en múltiples entradas o no tiene un valor principal para transformar, probablemente sea más apropiado usar una función personalizada. Si necesita generar marcado complejo o controlar el flujo de la plantilla, considere una etiqueta personalizada.

Creación y registro de filtros

Hay varias formas de definir y registrar filtros personalizados en Latte.

Registro directo mediante addFilter()

La forma más sencilla de agregar un filtro es usar el método addFilter() directamente en el objeto Latte\Engine. Especifique el nombre del filtro (cómo se usará en la plantilla) y el PHP invocable correspondiente.

$latte = new Latte\Engine;

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

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

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

Uso en la plantilla:

{$name|initial}                 {* Imprime 'J.' si $name es 'John' *}
{$description|shortify}         {* Usa la longitud predeterminada 10 *}
{$description|shortify:50}      {* Usa la longitud 50 *}
{$prices|sum}                   {* Imprime la suma de los elementos en el array $prices *}

Paso de argumentos:

El valor a la izquierda de la barra vertical (|) siempre se pasa como el primer argumento a la función del filtro. Cualquier parámetro especificado después de los dos puntos (:) en la plantilla se pasa como los siguientes argumentos.

{$text|shortify:30}
// Llama a la función PHP shortify($text, 30)

Registro mediante extensión

Para una mejor organización, especialmente al crear conjuntos de filtros reutilizables o compartirlos como paquetes, la forma recomendada es registrarlos dentro de una extensión 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);

Este enfoque mantiene la lógica de su filtro encapsulada y el registro simple.

Uso del cargador de filtros

Latte permite registrar un cargador de filtros usando addFilterLoader(). Es un único PHP invocable al que Latte solicitará cualquier nombre de filtro desconocido durante la compilación. El cargador devuelve el PHP invocable del filtro o null.

$latte = new Latte\Engine;

// El cargador puede crear/obtener dinámicamente filtros invocables
$latte->addFilterLoader(function (string $name): ?callable {
	if ($name === 'myLazyFilter') {
		// Imagine aquí una inicialización costosa...
		$service = get_some_expensive_service();
		return fn($value) => $service->process($value);
	}
	return null;
});

Este método estaba destinado principalmente a la carga diferida de filtros con una inicialización muy costosa. Sin embargo, las prácticas modernas de inyección de dependencias generalmente manejan los servicios diferidos de manera más eficiente.

Los cargadores de filtros agregan complejidad y generalmente no se recomiendan en favor del registro directo mediante addFilter() o dentro de una extensión usando getFilters(). Use cargadores solo si tiene una razón seria y específica relacionada con problemas de rendimiento en la inicialización de filtros que no se pueden resolver de otra manera.

Filtros usando una clase con atributos

Otra forma elegante de definir filtros es usar métodos en su clase de parámetros de plantilla. Simplemente agregue el atributo #[Latte\Attributes\TemplateFilter] al método.

use Latte\Attributes\TemplateFilter;

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

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

// Pasar el objeto a la plantilla
$params = new TemplateParameters(description: '...');
$latte->render('template.latte', $params);

Latte reconocerá y registrará automáticamente los métodos marcados con este atributo cuando el objeto TemplateParameters se pase a la plantilla. El nombre del filtro en la plantilla será el mismo que el nombre del método (shortify en este caso).

{* Uso del filtro definido en la clase de parámetros *}
{$description|shortify:50}

Filtros contextuales

A veces, un filtro necesita más información que solo el valor de entrada. Puede necesitar conocer el tipo de contenido de la cadena con la que está trabajando (por ejemplo, HTML, JavaScript, texto plano) o incluso modificarlo. Esta es la situación para los filtros contextuales.

Un filtro contextual se define igual que un filtro normal, pero su primer parámetro debe ser tipado como Latte\Runtime\FilterInfo. Latte reconoce automáticamente esta firma y pasa el objeto FilterInfo al llamar al filtro. Los siguientes parámetros reciben los argumentos del filtro como de costumbre.

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

$latte->addFilter('money', function (FilterInfo $info, float $amount): string {
	// 1. Verifique el tipo de contenido de entrada (opcional, pero recomendado)
	//    Permita null (entrada variable) o texto plano. Rechace si se aplica a HTML, etc.
	if (!in_array($info->contentType, [null, ContentType::Text], true)) {
		$actualType = $info->contentType ?? 'mixed';
		throw new \RuntimeException(
			"Filtro |money usado en tipo de contenido incompatible $actualType. Se esperaba text o null."
		);
	}

	// 2. Realice la transformación
	$formatted = number_format($amount, 2, '.', ',') . ' EUR';
	$htmlOutput = '<i>' . htmlspecialchars($formatted) . '</i>'; // ¡Asegúrese de un escape adecuado!

	// 3. Declare el tipo de contenido de salida
	$info->contentType = ContentType::Html;

	// 4. Devuelva el resultado
	return $htmlOutput;
});

$info->contentType es una constante de cadena de Latte\ContentType (por ejemplo, ContentType::Html, ContentType::Text, ContentType::JavaScript, etc.) o null, si el filtro se aplica a una variable ({$var|filter}). Puede leer este valor para verificar el contexto de entrada y escribir en él para declarar el tipo de contexto de salida.

Al establecer el tipo de contenido en HTML, le está diciendo a Latte que la cadena devuelta por su filtro es HTML seguro. Latte entonces no aplicará su escape automático predeterminado a este resultado. Esto es crucial si su filtro genera marcado HTML.

Si su filtro genera HTML, usted es responsable de escapar correctamente cualquier dato de entrada utilizado en ese HTML (como en el caso de llamar a htmlspecialchars($formatted) arriba). Omitirlo puede crear vulnerabilidades XSS. Si su filtro devuelve solo texto plano, no necesita establecer $info->contentType.

Filtros en bloques

Todos los filtros aplicados a bloques deben ser contextuales. Esto se debe a que el contenido del bloque tiene un tipo de contenido definido (generalmente HTML), del cual el filtro debe ser consciente.

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

Los filtros contextuales proporcionan un control sólido sobre cómo se procesan los datos según su contexto, permiten funciones avanzadas y garantizan un comportamiento de escape correcto, especialmente al generar contenido HTML.

versión: 3.0