Prácticas para desarrolladores

Instalación

La mejor forma de instalar Latte es con Composer:

composer require latte/latte

Versiones de PHP admitidas (vale para las últimas versiones correctivas de Latte):

versión compatible con PHP
Latte 3.1 PHP 8.2 – 8.5
Latte 3.0 PHP 8.0 – 8.5

Cómo renderizar una plantilla

¿Cómo renderizar una plantilla? Basta con este código sencillo:

$latte = new Latte\Engine;
// directorio de la caché
$latte->setCacheDirectory('/path/to/tempdir');

$params = [ /* variables de la plantilla */ ];
// o $params = new TemplateParameters(/* ... */);

// renderiza a la salida
$latte->render('template.latte', $params);
// o renderiza a una variable
$output = $latte->renderToString('template.latte', $params);

Los parámetros pueden ser arrays o, mejor aún, un objeto, que aporta comprobación de tipos y sugerencias en el editor.

También encontrará ejemplos de uso en el repositorio Latte examples.

Rendimiento y caché

Las plantillas de Latte son extremadamente rápidas, porque Latte las compila directamente a código PHP y las guarda en caché en disco. Así no tienen ninguna sobrecarga adicional frente a las plantillas escritas en PHP puro.

La caché se regenera automáticamente cada vez que cambia el archivo fuente. Así puede editar cómodamente sus plantillas de Latte durante el desarrollo y ver los cambios de inmediato en el navegador. Puede desactivar esta función en un entorno de producción y ganar un poco de rendimiento:

$latte->setAutoRefresh(false);

Al desplegar en un servidor de producción, la generación inicial de la caché, sobre todo en aplicaciones grandes, puede tardar un rato, como es lógico. Latte lleva incorporada una prevención del cache stampede. Es la situación en la que el servidor recibe un gran número de peticiones concurrentes y, como la caché de Latte aún no existe, todas la generarían a la vez. Lo que dispara la CPU. Latte es listo y, cuando hay varias peticiones concurrentes, solo el primer hilo genera la caché; los demás esperan y luego la usan.

También puede pregenerar la caché durante el despliegue (por ejemplo, en un script de deploy) con el método Engine::warmupCache(). Compila la plantilla indicada a la caché por adelantado, de modo que el primer visitante no tenga que esperar: $latte->warmupCache('template.latte').

Formas de extender Latte

Latte se puede personalizar de varias maneras, desde auxiliares sencillos hasta construcciones del lenguaje totalmente nuevas. La página extender Latte las trata en detalle; aquí tiene un repaso rápido:

  • Filtros personalizados: para dar formato o transformar datos en la salida de la plantilla (por ejemplo, {$var|myFilter}).
  • Funciones personalizadas: para lógica propia que se llama dentro de las expresiones de la plantilla (por ejemplo, {myFunction($arg)}).
  • Etiquetas personalizadas: para construcciones del lenguaje totalmente nuevas ({mytag}...{/mytag} o n:mytag).
  • Pases del compilador: funciones que modifican el AST de la plantilla entre el análisis y la generación del código PHP (por ejemplo, optimizaciones o comprobaciones de seguridad).
  • Loaders personalizados: para cambiar cómo localiza y carga Latte los archivos de plantilla.

Si quiere reutilizar sus extensiones entre proyectos o compartirlas con otras personas, agrúpelas en una clase de extensión de Latte.

Parámetros como clase

Mejor que pasar las variables a la plantilla en forma de array es crear una clase. Gana una notación con tipos seguros, buenas sugerencias en el IDE y una forma de registrar filtros y funciones.

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

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

Desactivar el escapado automático de una variable

Si la variable contiene una cadena HTML, puede marcarla para que Latte no la escape automáticamente (y, por tanto, por partida doble). Así evita tener que indicar |noescape en la plantilla.

Lo más sencillo es envolver la cadena en un objeto Latte\Runtime\Html:

$params = [
	'articleBody' => new Latte\Runtime\Html($article->htmlBody),
];

Latte tampoco escapa ningún objeto que implemente la interfaz Latte\Runtime\HtmlStringable. Así puede crear su propia clase cuyo método __toString() devuelva código HTML que no se escapará automáticamente:

class Emphasis implements Latte\Runtime\HtmlStringable
{
	public function __construct(
		private string $str,
	) {
	}

	public function __toString(): string
	{
		return '<em>' . htmlspecialchars($this->str) . '</em>';
	}
}

$params = [
	'foo' => new Emphasis('hello'),
];

El método __toString debe devolver HTML correcto y encargarse de escapar los parámetros, ¡o podría surgir una vulnerabilidad XSS!

Cómo extender Latte con filtros, etiquetas, etc.

¿Cómo añadir a Latte un filtro, una función, una etiqueta propios, etc.? Lo descubrirá en el capítulo extender Latte. Si quiere reutilizar sus cambios en distintos proyectos o compartirlos con otras personas, debería crear una extensión.

Cualquier código en la plantilla: {php ...}

Dentro de la etiqueta {do} solo se pueden escribir expresiones de PHP, así que no puede insertar, por ejemplo, construcciones como if ... else ni sentencias terminadas en punto y coma.

Puede registrar, sin embargo, la extensión RawPhpExtension, que añade la etiqueta {php ...}. Con ella puede insertar cualquier código PHP. No está sujeto a ninguna regla del modo sandbox, así que su uso es responsabilidad del autor de la plantilla.

$latte->addExtension(new Latte\Essential\RawPhpExtension);

Comprobar el código generado

Latte compila las plantillas a código PHP. Naturalmente, se asegura de que el código generado sea sintácticamente válido. Ahora bien, al usar extensiones de terceros o RawPhpExtension, Latte no puede garantizar la corrección del archivo generado. Además, en PHP se puede escribir código sintácticamente correcto pero prohibido (por ejemplo, asignar un valor a la variable $this) que provoca un PHP Compile Error. Si escribe una operación así en una plantilla, acabará también en el código PHP generado. Como en PHP hay más de doscientas operaciones prohibidas distintas, Latte no pretende detectarlas. El propio PHP las señalará al renderizar, lo que normalmente no supone un problema.

Hay situaciones, sin embargo, en las que quiere saber ya durante la compilación de la plantilla que no contiene ningún PHP Compile Error. Sobre todo cuando los usuarios pueden editar las plantillas o cuando usa el Sandbox. En tal caso, haga que las plantillas se revisen durante la compilación. Puede activar esta funcionalidad con el método Engine::enablePhpLinter(). Como necesita llamar al binario de PHP para la comprobación, pase su ruta como parámetro:

$latte = new Latte\Engine;
$latte->enablePhpLinter('/path/to/php');

try {
	$latte->compile('home.latte');
} catch (Latte\CompileException $e) {
	// captura los errores de Latte y también el Compile Error de PHP
	echo 'Error: ' . $e->getMessage();
}

Configuración regional

Latte permite establecer la configuración regional, que afecta al formato de números y fechas y a la ordenación. Se define con el método setLocale(). El identificador sigue el estándar de etiquetas de idioma IETF, que usa la extensión intl de PHP. Consta de un código de idioma y, en su caso, un código de país; por ejemplo, en_US para el inglés de Estados Unidos, de_DE para el alemán de Alemania, etc.

$latte = new Latte\Engine;
$latte->setLocale('en_US');

La configuración regional afecta a los filtros localDate, sort, number y bytes.

Requiere la extensión intl de PHP. El ajuste en Latte no afecta a la configuración regional global de PHP.

Modo estricto

En el modo de análisis estricto, Latte comprueba que no falten etiquetas HTML de cierre y además desactiva el uso de la variable $this. Para activarlo:

$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::StrictParsing);

Para generar plantillas con la cabecera declare(strict_types=1), haga lo siguiente:

$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::StrictTypes);

Desde Latte 3.1, los tipos estrictos están activados de forma predeterminada. Puede desactivarlos con $latte->setFeature(Latte\Feature::StrictTypes, false).

Advertencias de migración

Latte 3.1 cambia el comportamiento de algunos atributos HTML. Por ejemplo, los valores null ahora descartan el atributo en lugar de imprimir una cadena vacía. Para encontrar con facilidad los puntos donde este cambio afecta a sus plantillas, puede activar las advertencias de migración:

$latte->setFeature(Latte\Feature::MigrationWarnings);

Con ellas activadas, Latte comprueba los atributos renderizados y emite una advertencia de usuario (E_USER_WARNING) si la salida difiere de la que habría producido Latte 3.0. Cuando encuentre una advertencia, aplique una de estas soluciones:

  1. Si la nueva salida es la correcta para su caso (por ejemplo, prefiere que el atributo desaparezca cuando es null), silencie la advertencia añadiendo el filtro |accept
  2. Si quiere que el atributo se renderice vacío (por ejemplo title="") en lugar de descartarse cuando la variable es null, indique una cadena vacía como valor de repuesto: title={$val ?? ''}
  3. Si necesita estrictamente el comportamiento antiguo (por ejemplo, imprimir "1" para true en vez de "true"), convierta el valor a cadena de forma explícita: data-foo={(string) $val}

Una vez resueltas todas las advertencias, desactive las advertencias de migración y elimine todos los filtros |accept de sus plantillas, ya que dejan de ser necesarios.

Variables de bucle con ámbito

De forma predeterminada, las variables definidas en un bucle {foreach} (como $key y $value) siguen siendo accesibles después de terminar el bucle, igual que en el propio PHP. Esto puede provocar sobrescrituras involuntarias cuando una variable del bucle tiene el mismo nombre que una variable existente de la plantilla.

La función ScopedLoopVariables limita el ámbito de las variables del bucle al cuerpo del bucle. Al terminar, se restaura el valor original de la variable (si existía antes) o la variable queda sin definir:

$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::ScopedLoopVariables);

Ejemplo de la diferencia:

{var $item = 'original'}
{foreach [1, 2] as $item}{$item}, {/foreach}
{$item}

Sin ScopedLoopVariables: imprime 1, 2, 2 (la variable se sobrescribe) Con ScopedLoopVariables: imprime 1, 2, original (la variable se restaura)

Esto funciona también con la sintaxis de desestructuración, por ejemplo {foreach $array as [$a, $b]}.

Las variables de bucle que usan referencias ({foreach $array as &$value}) o asignaciones a propiedades ({foreach $array as $obj->prop}) no reciben ámbito propio, ya que eso rompería su finalidad.

Reducción automática de la sangría

Al usar etiquetas pares como {if}, {foreach} o {block}, es habitual sangrar el contenido anidado para hacerlo legible. Sin embargo, esa sangría se incluye de forma predeterminada en la salida generada. La función Dedent la elimina automáticamente, de modo que la salida queda limpia por muy profundamente que anide sus etiquetas de Latte:

$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::Dedent);

Ejemplo:

{if true}
	Hello
	World
{/if}

Sin Dedent, la salida incluiría la sangría (\tHello\n\tWorld\n). Con Dedent, la sangría se elimina y la salida es Hello\nWorld\n.

La sangría más profunda dentro de un bloque se conserva respecto a la sangría base:

{if true}
	Hello
		Indented
{/if}

Salida: Hello\n\tIndented\n.

La sangría dentro de un bloque debe ser coherente (o tabuladores o espacios). Si se mezclan, Latte lanza una excepción Inconsistent indentation.

Traducción en las plantillas

Use la extensión TranslatorExtension para añadir a la plantilla {_...}, {translate} y el filtro translate. Sirven para traducir valores o partes de la plantilla a otros idiomas. El parámetro es el callable que realiza la traducción, o un objeto de tipo Nette\Localization\Translator (pase null para desactivar las traducciones):

class MyTranslator
{
	public function __construct(private string $lang)
	{}

	public function translate(string $original): string
	{
		// crea $translated a partir de $original según $this->lang
		return $translated;
	}
}

$translator = new MyTranslator($lang);
$extension = new Latte\Essential\TranslatorExtension(
	$translator->translate(...), // [$translator, 'translate'] en PHP 8.0
);
$latte->addExtension($extension);

El traductor se llama en tiempo de ejecución, al renderizar la plantilla. Latte puede, sin embargo, traducir todos los textos estáticos durante la compilación de la plantilla. Esto ahorra rendimiento, porque cada cadena se traduce una sola vez y la traducción resultante se escribe en el archivo compilado. Así se crean varias versiones compiladas de la plantilla en el directorio de caché, una por idioma. Para ello basta con indicar el idioma como segundo parámetro:

$extension = new Latte\Essential\TranslatorExtension(
	$translator->translate(...),
	$lang,
);

Por texto estático entendemos, por ejemplo, {_'hello'} o {translate}hello{/translate}. El texto no estático, como {_$foo}, se seguirá traduciendo en tiempo de ejecución.

La plantilla puede pasar además parámetros adicionales al traductor mediante {_$original, foo: bar} o {translate foo: bar}, que este recibe como el array $params:

public function translate(string $original, ...$params): string
{
	// $params['foo'] === 'bar'
}

Depuración y Tracy

Latte trata de hacer el desarrollo lo más agradable posible. Para depurar existen tres etiquetas: {dump}, {debugbreak} y {trace}.

La mayor comodidad la conseguirá instalando la estupenda herramienta de depuración Tracy y activando el plugin de Latte:

// activa Tracy
Tracy\Debugger::enable();

$latte = new Latte\Engine;
// activa la extensión de Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);

Verá entonces todos los errores en una elegante pantalla roja, incluidos los errores de las plantillas con resaltado de fila y columna (vídeo). Al mismo tiempo, en la esquina inferior derecha, en la llamada barra de Tracy, aparece una pestaña para Latte donde ve con claridad todas las plantillas renderizadas y sus relaciones (con la posibilidad de saltar a la plantilla o al código compilado), así como las variables:

Como Latte compila las plantillas a código PHP legible, puede recorrerlas cómodamente paso a paso en su IDE.

Linter: validar la sintaxis de las plantillas

La herramienta Linter sirve para validar todas las plantillas. Su cometido es recorrer los archivos indicados y comprobar que no contienen errores de sintaxis ni referencias a etiquetas, filtros, funciones, clases o construcciones similares inexistentes.

El Linter se ejecuta desde la línea de comandos:

vendor/bin/latte-lint <path>

Use el parámetro --strict para activar el Modo estricto. El parámetro --debug imprime el nombre de cada archivo procesado y los detalles completos de las excepciones, lo que ayuda a diagnosticar problemas.

Si usa etiquetas, filtros u otras extensiones propias de Latte, necesita crear su propia variante del Linter, por ejemplo custom-latte-lint. En ese script registra todas las extensiones necesarias antes de que se valide realmente ninguna plantilla:

#!/usr/bin/env php
<?php

// indique la ruta real al archivo autoload.php
require __DIR__ . '/vendor/autoload.php';

$path = $argv[1] ?? '.';

$linter = new Latte\Tools\Linter;
$latte = $linter->getEngine();
// añada aquí sus propias extensiones
$latte->addExtension(/* ... */);

$ok = $linter->scanDirectory($path);
exit($ok ? 0 : 1);

Como alternativa, puede pasar al Linter su propio objeto Latte\Engine:

$latte = new Latte\Engine;
// aquí configuramos el objeto $latte
$linter = new Latte\Tools\Linter(engine: $latte);

El linter personalizado resultante se usa igual que la herramienta estándar, pero con pleno conocimiento de todas sus extensiones propias.

Cargar plantillas desde una cadena

¿Necesita cargar las plantillas desde cadenas en lugar de archivos, quizá para hacer pruebas? StringLoader le ayudará:

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

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

Manejador de excepciones

Puede definir su propio manejador para las excepciones esperadas. Se le pasan las excepciones lanzadas dentro de {try} y en el sandbox.

$loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) {
	$logger->log($e);
};

$latte = new Latte\Engine;
$latte->setExceptionHandler($loggingHandler);

Búsqueda automática del layout

Con la etiqueta {layout}, la plantilla determina su plantilla padre. También es posible hacer que el layout se busque automáticamente, lo que simplifica la escritura de plantillas, ya que no necesitarán incluir la etiqueta {layout}.

Se consigue así:

// devuelve la ruta al archivo de la plantilla padre
$finder = fn(Latte\Runtime\Template $template) => 'automatic.layout.latte';
$latte = new Latte\Engine;
$latte->addProvider('coreParentFinder', $finder);

Si la plantilla no debe tener layout, lo indicará con la etiqueta {layout none}.

versión: 3.x