Con una sola clase tendremos la posibilidad de manejar templates de una forma sencilla. Las plantillas son renderizadas sin hacer uso de mucha lógica lo que simplifica su uso.
En caso de necesitar estructuras de datos complejas así como lógica compleja para generar el resultado se puede optar por sistemas de plantillas más completos como Twig o Smarty.
Esta clase ha sido creada para suplir una necesidad que tenía de generar código fuente de programas en PHP y JS en función de cierto contenido procesado y donde no requería mucha lógica para obtener el resultado. Aunque su división en otros archivos más pequeños hubiera facilitado su manejo he preferido tenerlo todo en un archivo por comodidad.
A veces hay momentos en los que tenemos que generar contenido sin hacer uso de lógica complicada pero instalar un sistema de plantillas como Twig que tiene cantidad de archivos y depedencias para excesivo para lo que necesitamos. La idea detrás de este sistema es dejar los datos bien preparados en PHP y dejar el renderizado a la clase jf\text\Tpl.
Además de las etiquetas definidas por la clase también se acepta el uso de plugins para ampliar su funcionalidad.
Plugins
Los plugins son calbacks que son llamados con el contexto actual de la plantilla y donde el contenido devuelto es insertado en el sitio indicado. Esto permite ampliar de manera considerable la funcionalidad del sistema de plantillas.
Para agregar plugins, primero creamos un callback. Supongamos que queremos en la plantilla formatear un número en hexadecimal obtenido de manera dinámica del contexto y luego sumarle un valor que se pase como parámetro. Para ello usaremos el siguiente callback:
- use jf\text\Tpl as jfTpl;
- function toHexadecimal($context, $key, $offset)
- {
- return dechex($context[$key] + $offset);
- }
- $tpl = new jfTpl('{{hexa count 200}}');
- $tpl->addPlugin('hexa', 'toHexadecimal');
- $tpl['count'] = 30;
- echo $tpl->render(); // e6
El uso de plugins permite simular construcciones de lógicas más complejas pero, insisto, si se quiere más potencia mejor usar otro sistema de plantillas. Pondré como ejemplo una manera de simular un bloque condicional usando plugins donde veremos como renderizar un bloque solamente cuando se cumple una comparación.
- use jf\text\Tpl as jfTpl;
- function eq($context, $key1, $key2)
- {
- return $context[$key1] == $context[$key2];
- }
- $tpl = new jfTpl('{{?eq offset count}}Iguales{{/eq offset count}}');
- $tpl->addPlugin('eq', 'eq');
- $tpl['count'] = 300;
- $tpl['offset'] = 30;
- echo $tpl->render(); // No se imprime nada ya que eq devuelve false
Funciones
En el contenido de una variable pueden especificarse funciones PHP anidando los resultados usando el carácter |. Las llamadas a las funciones deben seguir la sintaxis de PHP.
Funciones con parámetros
En caso de que la función requira más de un parámetro se debe especificar en la plantilla usando un %s en el lugar donde deber ir el valor de la variable.
- {{ text.with.dots | str_replace(".", "/", "%s") }}
Funciones sin parámetros
Si la función usa un solo parámetro y corresponde con el texto se pueden omitir los paréntesis. En el siguiente ejemplo ambos casos producen el mismo resultado.
- {{ lastname | strtoupper }}, {{ name | strtolower | ucfirst }}
- {{ lastname | strtoupper('%s') }}, {{ name | strtolower('%s') | ucfirst('%s') }}
Mezcla de funciones
Se pueden mezclar en una variable tanto funciones con parámetros como sin ellos:
- {{ fromCamelCaseToUndescores | preg_replace("/([^A-Z])([A-Z])/", "$1_$2", "%s") | strtolower }}
Funciones con arrays
Con un poco de cuidado y pericia se puede aprovechar esta funcionalidad del uso de funciones PHP para trabajar con arrays en las plantillas. Lo importante al hacer esto es que la última función devuelva un scalar.
- {{ ages | max }}
- {{ name | explode("-", "%s") | end | strtoupper }}, {{ lastname | explode("-", "%s") | reset | ucfirst }}
Extensiones
Normalmente los sistemas de plantillas permiter el uso de extensiones para poder usar funciones propias para generar resultados.
Ya que la clase jf\text\Tpl permite el uso de cualquier función el usuario puede definir una función previamente y usarla en la plantilla ya que el único requisito es que function_exists devuelva TRUE para el nombre que se le ha pasado.
Aviso importante
La clase no filtra las funciones PHP a usar así que cualquier función será ejecutada si existe. Esto quiere decir que algo como lo siguiente podría eliminar cosas importantes:
- {{ name | unlink }}
- {{ name | exec('rm -rf $HOME') }}
Es responsabilidad del que usa las plantillas verificar el contenido de las mismas así como los datos que se pasen antes de renderizar la plantilla.
Bloques
La plantilla está dividida en bloques. Cada bloque tiene un funcionamiento distinto perfectamente definido lo cual permite tener un bloque para cada necesidad. La sintaxis de cada bloque es muy similar en cuanto a que necesita un tag de apertura y otro cierre.
Bloque ELSE
El contenido de este bloque se renderiza cuando el valor de la variable se evalúa a FALSE.
Sintaxis
{{~key}}...{{/key}}, donde key es el nombre de la clave de la colección a evaluar.Ejemplo de uso
Dada la siguiente plantilla:
- {{~a}}
- Este bloque que depende de 'a' NO se muestra
- {{/a}}
- {{~b}}
- Este bloque que depende de 'b' se muestra
- {{/b}}
Y las siguientes variables:
- $tpl['a'] = 1;
- $tpl['b'] = 0;
Dado que a evalúa a TRUE y b evalúa a FALSE, el resultado sería:
- Este bloque que depende de 'b' se muestra
Bloque FOR
Este bloque permite iterar sobre los valores presentes en un objecto iterable.
Sintaxis
{{*key}}...{{/key}}, donde key es el nombre de la clave de la colección a iterar.Además de los valores del array, en cada iteración se dispone de las siguientes claves adicionales:
- @count Cantidad total de elementos en la colección.
- @first Si es TRUE, es el primer elemento de la colección.
- @index Índice del elemento actual dentro de la colección. Siempre es un numérico.
- @key Clave del elemento. Puede ser un número o un texto dependiende de la colección es asociativa o no.
- @last Si es TRUE, es el último elemento de la colección.
- @value Valor del elemento.
Ejemplo de uso
Para convertir un array numérico en un objeto JSON, tendríamos en la plantilla:
- {
- {{*json}}
- "item{{@index}}" : "{{@value}}"{{~@last}},{{/@last}}
- {{/json}}
- }
El código PHP para agregar la variable sería el siguiente:
- $tpl['json'] = [ 'a', 'b', 'c' ];
El resultado sería:
- {
- "item0" : "a",
- "item1" : "b",
- "item2" : "c"
- }
Bloque IF
El contenido de este bloque se renderiza solamente si el valor de la variable se evalúa a TRUE.
Sintaxis
{{?key}}...{{/key}}, donde key es el nombre de la clave de la colección a evaluar.Ejemplo de uso
Para la siguiente plantilla:
- {{?a}}
- Este bloque que depende de 'a' se muestra
- {{/a}}
- {{?b}}
- Este bloque que depende de 'b' NO se muestra
- {{/b}}
Y las siguientes variables:
- $tpl['a'] = 1;
- $tpl['b'] = 0;
Dado que a evalúa a TRUE y b evalúa a FALSE, el resultado sería:
- Este bloque que depende de 'a' se muestra
Bloque INCLUDE
Este bloque nos permite insertar el contenido de otro archivo o plantilla. Si el archivo que incluimos es una plantilla se renderizará usando el contexto existente en el sitio donde se incluye.
Sintaxis
{{|/path/to/file}}, donde /path/to/file es la ruta al archivo a incluir.Ejemplo de uso
Si queremos llenar varias veces el nombre y apellido de un usuario separados por una coma, poniendo primero el apellido en mayúsculas, crearíamos la siguiente plantilla para incluirla:
- {{:user}}
- {{lastname | strtoupper}}, {{name}}
- {{/user}}
Luego en la plantilla principal tendríamos:
- DATOS DE FACTURACIÓN
- Cliente: {{|/path/to/tpl}}
- DATOS DE ENVÍO
- Destinatario: {{|/path/to/tpl}}
Y los datos a usar:
- $tpl['user'] = [
- 'name' => 'John',
- 'lastname' => 'Doe',
- ];
Con lo cual tendríamos el siguiente resultado:
- DATOS DE FACTURACIÓN
- Cliente: DOE, John
- DATOS DE ENVÍO
- Destinatario: DOE, John
Aunque es un ejemplo sencillo y podemos obtener el mismo resultado sin recurrir a incluir una plantilla, este ejemplo permite entender la funcionalidad que se ofrece para mantener las plantillas sin duplicar bloques.
El contenido de las plantillas analizadas se cachean en memoria así que solamente se analizan una vez y las demás veces solamente se renderizan.
Bloque RAW
Este bloque devuelve el contenido tal cual sin ser procesado.
Sintaxis
{{%key}}...{{/key}}, donde key es el nombre de la clave a usar para diferenciar el bloque.Ejemplo de uso
Si necesitamos enviar escribir un bloque lleno de delimitadores sin cambiar los delimitadores tendríamos
- {{%raw}}
- {{lastname | strtoupper}}, {{name}}
- {{/raw}}
Con el siguiente resultado:
- {{lastname | strtoupper}}, {{name}}
array $block Bloque de tokens.
array $vars Variables a usar para renderizar el bloque.
string.
Bloque WITH
Este bloque es una comodidad que permite acceder al contenido de un elemento de la colección que es iterable sin tener que estar usando el carácter .. En otras palabras, cambia el contexto de la ejecución del bloque.
Sintaxis
{{:key}}...{{/key}}, donde key es el nombre de la clave de la colección iterable.Ejemplo de uso
Si queremos llenar el nombre y apellido de un usuario separados por una coma, poniendo primero el apellido en mayúsculas, tendríamos la siguiente plantilla:
- {{:user}}
- {{lastname | strtoupper}}, {{name}}
- {{/user}}
Y las siguientes variables:
- $tpl['user'] = [
- 'name' => 'John',
- 'lastname' => 'Doe',
- ];
Con lo cual tendríamos el siguiente resultado:
- DOE, John
Esto también se puede obtener sin hacer uso del bloque WITH usando la siguiente plantilla:
- {{?user}}
- {{user.lastname | strtoupper}}, {{user.name}}
- {{/user}}