Loaders

Los loaders son el mecanismo con el que Latte obtiene el código fuente de sus plantillas. Lo más habitual es que las plantillas sean archivos guardados en disco, pero el flexible sistema de loaders de Latte le permite cargarlas prácticamente desde cualquier sitio, o incluso generarlas dinámicamente.

¿Qué es un loader?

Normalmente, cuando trabaja con plantillas, piensa en archivos .latte situados en la estructura de directorios de su proyecto. De eso se encarga el FileLoader predeterminado de Latte. Sin embargo, la relación entre el nombre de una plantilla (como 'main.latte' o 'components/card.latte') y el contenido real de su código fuente no tiene por qué ser una correspondencia directa con una ruta de archivo.

Aquí es donde entran los loaders. Un loader es un objeto responsable de tomar el nombre de una plantilla (una cadena identificadora) y proporcionar a Latte su código fuente. Latte confía por completo en el loader configurado para esta tarea. Esto vale no solo para la plantilla inicial solicitada con $latte->render('main.latte'), sino también para todas las plantillas referenciadas dentro mediante etiquetas como {include ...}, {layout ...}, {embed ...} o {import ...}.

¿Por qué usar un loader propio?

  • Cargar desde fuentes alternativas: obtener plantillas guardadas en una base de datos, en una caché (como Redis o Memcached), en un sistema de control de versiones (como Git, a partir de un commit concreto) o generadas dinámicamente.
  • Aplicar convenciones de nombres propias: quizá quiera usar alias más cortos para las plantillas o implementar una lógica concreta de rutas de búsqueda (por ejemplo, mirar primero en el directorio del tema y recurrir después a un directorio predeterminado).
  • Añadir seguridad o control de acceso: un loader propio podría verificar los permisos del usuario antes de cargar determinadas plantillas.
  • Preprocesado: aunque en general no se recomienda (son mejores los pases del compilador), en teoría un loader podría preprocesar el contenido de la plantilla antes de entregárselo a Latte.

El loader de una instancia de Latte\Engine se establece con el método setLoader():

$latte = new Latte\Engine;

// Uso del FileLoader predeterminado para los archivos de '/path/to/templates'
$loader = new Latte\Loaders\FileLoader('/path/to/templates');
$latte->setLoader($loader);

Un loader debe implementar la interfaz Latte\Loader.

Loaders integrados

Latte ofrece varios loaders estándar:

FileLoader

Es el loader predeterminado que usa la clase Latte\Engine si no se indica otro. Carga las plantillas directamente del sistema de archivos.

Opcionalmente puede fijar un directorio raíz para restringir el acceso:

use Latte\Loaders\FileLoader;

// Lo siguiente solo permitirá cargar plantillas del directorio /var/www/html/templates
$loader = new FileLoader('/var/www/html/templates');
$latte->setLoader($loader);

// $latte->render('../../../etc/passwd'); // Esto lanzaría una excepción

// Renderizado de una plantilla situada en /var/www/html/templates/pages/contact.latte
$latte->render('pages/contact.latte');

Al usar etiquetas como {include} o {layout}, resuelve los nombres de plantilla de forma relativa a la plantilla actual, salvo que se indique una ruta absoluta. Ahora bien, si se ha fijado un directorio raíz, todos los nombres se resuelven de forma relativa a la plantilla actual.

StringLoader

Este loader obtiene el contenido de las plantillas de un array asociativo, donde las claves son los nombres (identificadores) de las plantillas y los valores, las cadenas con su código fuente. Resulta especialmente útil para pruebas o aplicaciones pequeñas, en las que las plantillas pueden estar guardadas directamente en el código PHP.

use Latte\Loaders\StringLoader;

$loader = new StringLoader([
	'main.latte' => 'Hello {$name}, include is below:{include helper.latte}',
	'helper.latte' => '{var $x = 10}Included content: {$x}',
	// Añada más plantillas según haga falta
]);

$latte->setLoader($loader);

$latte->render('main.latte', ['name' => 'World']);
// Salida: Hello World, include is below:Included content: 10

Si solo necesita renderizar una única plantilla directamente desde una cadena, sin includes ni herencia que referencien otras plantillas de cadena con nombre, puede pasar la cadena directamente al método render() o renderToString() usando StringLoader sin array:

$loader = new StringLoader;
$latte->setLoader($loader);

$templateString = 'Hello {$name}!';
$output = $latte->renderToString($templateString, ['name' => 'Alice']);
// $output contiene 'Hello Alice!'

Crear un loader propio

Para crear su propio loader (por ejemplo, para cargar plantillas desde una base de datos, una caché, un sistema de control de versiones u otra fuente), debe crear una clase que implemente la interfaz Latte\Loader.

Veamos qué debe hacer cada método.

getContent (string $name)string

Es el método central del loader. Su tarea es obtener y devolver el código fuente completo de la plantilla identificada por $name (tal como se pasó al método $latte->render() o como lo devolvió el método getReferredName()).

Si no se puede encontrar la plantilla o acceder a ella, este método debe lanzar Latte\TemplateNotFoundException.

public function getContent(string $name): string
{
	// Ejemplo: carga desde un almacenamiento interno hipotético
	$content = $this->storage->read($name);
	if ($content === null) {
		throw new Latte\TemplateNotFoundException("Template '$name' cannot be loaded.");
	}
	return $content;
}

getReferredName (string $name, string $referringName)string

Este método se encarga de resolver los nombres de plantilla usados dentro de etiquetas como {include}, {layout}, etc. Cuando Latte encuentra, por ejemplo, {include 'partial.latte'} dentro de main.latte, llama a este método con $name = 'partial.latte' y $referringName = 'main.latte'.

La tarea del método es resolver $name en un identificador canónico (por ejemplo, una ruta absoluta o una clave única de base de datos) que se usará al llamar a los demás métodos del loader, según el contexto que aporta $referringName.

public function getReferredName(string $name, string $referringName): string
{
	return ...;
}

getUniqueId (string $name)string

Latte usa una caché de plantillas compiladas para mejorar el rendimiento. Cada archivo de plantilla compilada necesita un nombre único derivado del identificador de la plantilla de origen. Este método proporciona una cadena que identifica de forma única la plantilla $name.

Para las plantillas basadas en archivos, la ruta absoluta puede cumplir ese papel. Para las plantillas en base de datos, lo habitual es una combinación de un prefijo y el ID de la base de datos.

public function getUniqueId(string $name): string
{
	return ...;
}

Ejemplo: loader sencillo de base de datos

Este ejemplo muestra la estructura básica de un loader que carga plantillas guardadas en una tabla de base de datos llamada templates, con las columnas name (identificador único), content y updated_at.

use Latte;

class DatabaseLoader implements Latte\Loader
{
	public function __construct(
		private \PDO $db,
	) {
	}

	public function getContent(string $name): string
	{
		$stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?');
		$stmt->execute([$name]);
		$content = $stmt->fetchColumn();
		if ($content === false) {
			throw new Latte\TemplateNotFoundException("Template '$name' not found in database.");
		}
		return $content;
	}

		// Este ejemplo sencillo da por hecho que los nombres de las plantillas ('homepage', 'article', etc.)
		// son IDs únicos y que las plantillas no se referencian entre sí de forma relativa.
	public function getReferredName(string $name, string $referringName): string
	{
		return $name;
	}

	public function getUniqueId(string $name): string
	{
			// Aquí basta con usar un prefijo y el propio nombre, que es único
		return 'db_' . $name;
	}
}

// Uso:
$pdo = new \PDO(/* datos de la conexión */);
$loader = new DatabaseLoader($pdo);
$latte->setLoader($loader);
$latte->render('homepage'); // Carga de la BD la plantilla llamada 'homepage'

Los loaders propios le dan control completo sobre el origen de sus plantillas de Latte y permiten integrarlas con distintos sistemas de almacenamiento y flujos de trabajo.

versión: 3.x