Ampliación de Latte
Latte es muy flexible y se puede ampliar de muchas maneras: puede añadir filtros personalizados, funciones, etiquetas, cargadores, etc. Le mostraremos cómo hacerlo.
Este capítulo describe las diferentes formas de extender Latte. Si quieres reutilizar tus cambios en diferentes proyectos o si quieres compartirlos con otros, entonces deberías crear la llamada extensión.
¿Cuántos caminos llevan a Roma?
Dado que algunas de las formas de extender Latte pueden mezclarse, intentemos primero explicar las diferencias entre ellas. Como ejemplo, intentemos implementar un generador Lorem ipsum, al que se le pasa el número de palabras a generar.
La principal construcción del lenguaje Latte es la etiqueta. Podemos implementar un generador extendiendo Latte con una nueva etiqueta:
{lipsum 40}
La etiqueta funcionará muy bien. Sin embargo, el generador en forma de etiqueta puede no ser lo suficientemente flexible porque no se puede utilizar en una expresión. Por cierto, en la práctica, rara vez necesitarás generar etiquetas; y eso es una buena noticia, porque las etiquetas son una forma más complicada de extender.
Bien, intentemos crear un filtro en lugar de una etiqueta:
{=40|lipsum}
De nuevo, una opción válida. Pero el filtro debería transformar el valor pasado en otra cosa. Aquí usamos el valor
40
, que indica el número de palabras generadas, como argumento del filtro, no como el valor que queremos
transformar.
Así que intentemos usar la función
{lipsum(40)}
¡Eso es! Para este ejemplo en particular, crear una función es el punto de extensión ideal a utilizar. Puedes llamarla en cualquier lugar donde se acepte una expresión, por ejemplo:
{var $text = lipsum(40)}
Filtros
Crea un filtro registrando su nombre y cualquier llamada PHP, como una función:
$latte = new Latte\Engine;
$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // acorta el texto a 10 caracteres
En este caso sería mejor que el filtro recibiera un parámetro adicional:
$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len));
Lo usamos en una plantilla como esta:
<p>{$text|shortify}</p>
<p>{$text|shortify:100}</p>
Como puede ver, la función recibe la parte izquierda del filtro antes de la tubería |
as the first argument and
the arguments passed to the filter after :
como los siguientes argumentos.
Por supuesto, la función que representa el filtro puede aceptar cualquier número de parámetros, y también se admiten parámetros variádicos.
Si el filtro devuelve una cadena en HTML, puede marcarla para que Latte no la escape automáticamente (y por tanto por partida
doble). Esto evita la necesidad de especificar |noescape
en la plantilla. La forma más fácil es envolver la cadena
en un objeto Latte\Runtime\Html
, la otra forma es Filtros contextuales.
$latte->addFilter('money', fn(float $amount) => new Latte\Runtime\Html("<i>$amount EUR</i>"));
En este caso, el filtro debe garantizar el correcto escape de los datos.
Filtros que utilizan la clase
La segunda forma de definir un filtro es utilizar la clase. Creamos un
método con el 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);
Cargador de filtros
En lugar de registrar filtros individuales, puede crear un llamado cargador, que es una función que se llama con el nombre del filtro como argumento y devuelve su PHP callable, o null.
$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 contextuales
Un filtro contextual es aquel que acepta un objeto Latte\Runtime\FilterInfo en el primer parámetro, seguido de otros parámetros como en el caso de los filtros clásicos. Se registra de la misma manera, el propio Latte reconoce que el filtro es contextual:
use Latte\Runtime\FilterInfo;
$latte->addFilter('foo', function (FilterInfo $info, string $str): string {
// ...
});
Los filtros contextuales pueden detectar y cambiar el tipo de contenido que reciben en la variable
$info->contentType
. Si el filtro se llama clásicamente sobre una variable (por ejemplo {$var|foo}
),
el $info->contentType
contendrá null.
El filtro debe comprobar primero si el tipo de contenido de la cadena de entrada es compatible. También puede cambiarlo. Ejemplo de un filtro que acepta texto (o null) y devuelve HTML:
use Latte\Runtime\FilterInfo;
$latte->addFilter('money', function (FilterInfo $info, float $amount): string {
// first we check if the input's content-type is text
if (!in_array($info->contentType, [null, ContentType::Text])) {
throw new Exception("Filter |money used in incompatible content type $info->contentType.");
}
// change content-type to HTML
$info->contentType = ContentType::Html;
return "<i>$amount EUR</i>";
});
En este caso, el filtro debe garantizar el correcto escapado de los datos.
Todos los filtros que se utilicen sobre bloques (por ejemplo, como
{block|foo}...{/block}
) deben ser contextuales.
Funciones
Por defecto, todas las funciones nativas de PHP pueden ser usadas en Latte, a menos que el sandbox lo deshabilite. Pero también puede definir sus propias funciones. Estas pueden sobreescribir las funciones nativas.
Crear una función mediante el registro de su nombre y cualquier PHP callable:
$latte = new Latte\Engine;
$latte->addFunction('random', function (...$args) {
return $args[array_rand($args)];
});
El uso es entonces el mismo que cuando se llama a la función PHP:
{random(apple, orange, lemon)} // prints for example: apple
Funciones usando la clase
La segunda forma de definir una función es utilizar la clase. Creamos un
método con el 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);
Cargadores
Los cargadores se encargan de cargar plantillas desde una fuente, como un sistema de archivos. Se configuran utilizando el
método setLoader()
:
$latte->setLoader(new MyLoader);
Los cargadores incorporados son:
Cargador de archivos
Cargador por defecto. Carga plantillas desde el sistema de archivos.
El acceso a los archivos puede restringirse estableciendo el directorio base:
$latte->setLoader(new Latte\Loaders\FileLoader($templateDir));
$latte->render('test.latte');
StringLoader
Carga plantillas a partir de cadenas. Este cargador es muy útil para pruebas unitarias. También se puede utilizar para proyectos pequeños donde puede tener sentido almacenar todas las plantillas en un único archivo 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);
Creación de un cargador personalizado
Loader es una clase que implementa la interfaz Latte\Loader.
Etiquetas
Una de las características más interesantes del motor de plantillas es la posibilidad de definir nuevas construcciones del lenguaje mediante etiquetas. También es una funcionalidad más compleja y es necesario entender cómo funciona internamente Latte.
En la mayoría de los casos, sin embargo, la etiqueta no es necesaria:
- si debe generar alguna salida, utilice function en su lugar
- si fuera a modificar alguna entrada y devolverla, use filtro en su lugar
- si fuera a editar un área de texto, envuélvalo con una etiqueta
{block}
y utilice un filtro - si no se supone que debe generar ninguna salida sino sólo llamar a una función, llámela con
{do}
Si todavía quieres crear una etiqueta, ¡genial! Todo lo esencial se puede encontrar en Creación de una Extensión.
Pases de compilador
Los pases de compilador son funciones que modifican ASTs o recogen información en ellos. En Latte, por ejemplo, un sandbox se implementa de esta manera: recorre todos los nodos de un AST, encuentra llamadas a funciones y métodos, y los reemplaza por llamadas controladas.
Al igual que con las etiquetas, se trata de una funcionalidad más compleja y es necesario entender cómo funciona Latte bajo el capó. Todo lo esencial se puede encontrar en el capítulo Creación de una Extensión.