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}on: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:
- 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 - Si quiere que el atributo se renderice vacío (por ejemplo
title="") en lugar de descartarse cuando la variable esnull, indique una cadena vacía como valor de repuesto:title={$val ?? ''} - Si necesita estrictamente el comportamiento antiguo (por ejemplo, imprimir
"1"paratrueen 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}.