5 razones para no usar Smarty (o similares)

Ejemplo de uso de SmartyEn la mayoría de los casos, Smarty no es la solución idónea para separar la lógica de las vistas, aunque hay que admitir que la versión 3 está siendo un salto de calidad impresionante. No obstante, estas son las cinco principales razones que he encontrado para no incluirlo más en mis proyectos:

  1. PHP es un lenguaje con sintaxis embebida, es decir, es de por sí un sistema de plantillas. Para mayor claridad puedes utilizar la tag de apertura corta (<?en lugar de<?php) y el operador de impresión (<?=$nombre ?> en lugar de <?php echo $nombre ?>). Además, Smarty no añade funcionalidad.
  2. Con Smarty debes aprender una nueva sintaxis para imprimir variables, hacer bucles, etc.
  3. En aplicaciones complejas tendrás que crear un montón de plug-ins para cargar widgets u otros fragmentos de código para las vistas que quieras reutilizar. O bien utilizar cada dos por tres la tag {php}.
  4. Smarty es difícil de integrar en gestores de contenido, frameworks y editores de código.
  5. Smarty reduce el rendimiento de tus aplicaciones web ya que debe parsear y convertir a PHP las plantillas. Incluso con caché el rendimiento se ve afectado (más accesos a disco).

Ya habíamos hablado de este tema en Plantillas PHP: cuestión de rendimiento, proponiendo el funcionamiento básico de un motor de plantillas basado en PHP puro.

ACTUALIZACIÓN (23 de noviembre de 2009):

Algunos expertos han escrito artículos en profundidad sobre este tema, por ejemplo “Template Engines” de Brian Lozer.

Con respecto a las alternativas, Savant y PHPTemplate (creado para Drupal) son buenas opciones. Además, los principales frameworks (CakePHP, etc) y gestores de contenido (Joomla!, WordPress) suelen incluir sus propios motores de plantillas en PHP.

«In short, the point of template engines should be to separate your business logic from your presentation logic, not separate your PHP code from your HTML code.»

ACTUALIZACIÓN (31 de enero de 2013):

En este artículo no defiendo que los sistemas de plantillas sean malos siempre. No son ni malos por naturaleza, y hay muchos tipos de proyectos en los que tiene sentido usarlos. Personalmente no los he necesitado nunca, aunque si tengo que elegir uno elijo Twig, sin duda.

Plantillas PHP: cuestión de rendimiento

He leído dos posts bastante viejos (2006 y 2007) del gran Ricardo Galli sobre el rendimiento y escalabilidad de Twitter y los sistemas de plantillas PHP. En ambos posts las discusiones han sido muy ricas, y me llevan a dos grandes conclusiones: aunque el cache y escalabilidad horizontal son soluciones importantes para la alta disponibilidad de sitios web, es vital escribir buen código. Y en ese sentido, los sistemas de plantillas y los frameworks pueden jugar malas pasadas.

Plantillas PHP, sin pseudo-lenguajes

En posts recientes he hablado de Smarty y de PHPTAL, dos sistemas de plantillas PHP que añaden un lenguaje nuevo a nuestras aplicaciones web, ya cargadas con PHP, SQL, XHTML, CSS, JavaScript y nosecuantas siglas más. Eso significa que, además de la curva de aprendizaje, hay que cargar el proyecto con un parser del pseudo-lenguaje. En el caso de Smarty, dicen ser rápidos, aunque hay un fork del proyecto llamado TemplateLight que dice ser más liviano que su “papá”.

Existe un método para utilizar plantillas y no usar sistemas adicionales. Es una sistema sencillo, estúpidamente sencillo, pero eficiente y prácticamente con los mismos beneficios que usar Smarty u otros: plantillas PHP puro, embebiendo los códigos en los documentos HTML, pero separando las capas de lógica y vista para no caer en chapuzas.

Podemos crear una pequeña biblioteca de funciones para gestionar plantillas de este tipo y que los nombre de variables no causen conflictos. Utilizar plantillas PHP puro permite separar la presentación de la lógica, pero sin la bajada de rendimiento de los sistemas que parsean plantillas escritas en un pseudo-lenguaje como Smarty, del que ya se ha hablado por aquí. Reconozco que las plantillas PHP no tienen la legibilidad de Smarty o PHPTAL, pero se le acerca bastante.

simpletpl.php (la librería del motor)

<?php

//Directorio donde están las plantillas
define("TPL_DIR", "tpl");

//Array que alojará las variables de las plantillas
$tpl_vars = array();

//Asigna una variable a la plantilla
function tpl_asignar($nombre, $valor) {
global $tpl_vars;
$tpl_vars[$nombre] = $valor;
}

//Devuelve una variable de la plantilla
function tpl($variable) {
global $tpl_vars;
if (isset($tpl_vars[$variable])) return $tpl_vars[$variable];
else trigger_error("La variable '$variable' no existe", E_USER_ERROR);
}

//Carga la plantilla especificada
function tpl_cargar($plantilla) {
global $tpl_vars;
if (file_exists(TPL_DIR)) {
if (file_exists(TPL_DIR . "/$plantilla")) {
include(TPL_DIR . "/$plantilla");
} else trigger_error("La plantilla '$plantilla' no existe", E_USER_ERROR);
} else trigger_error("El directorio de plantillas especificado no existe. Verifique la constante TPL_DIR", E_USER_ERROR);
}

//Indenta un texto al $n - ésimo nivel
function tab($texto, $n) {
return preg_replace('/n/', "n" . str_repeat("t", $n), $texto);
}

?>

inicio.tpl.php (un ejemplo de plantilla cualquiera)

<html>
<head>
<title><?php echo tpl('titulo') ?></title>
</head>
<body>
<h1><?php echo tpl('titulo') ?></h1>

<?php foreach (tpl('posts') as $p) { ?>
<h2><a href="<?php echo $p['url'] ?>"><?php echo $p['titulo'] ?></a></h2>
<div style="color: gray"><?php echo $p['creado'] ?></div>

<?php echo tab($p['cuerpo'], 2) ?>

<?php } ?>

<?php tpl_cargar("pie.tpl.php") ?></pre>
<h3>pie.tpl.php (otra plantilla de ejemplo, para ser incluida)</h3>
1<div style="background: silver; padding: 10px">2008 © Israel Viana</div>
</body>
</html>

index.php (script que obtiene los datos y carga la plantilla)

<?php
include("simpletpl.php");
$conexion = mysql_connect("localhost", "root", "root");
mysql_select_db("blog");

$q = mysql_query("SELECT * FROM posts ORDER BY id DESC LIMIT 5");

$posts = array();
while ($p = mysql_fetch_assoc($q)) {
$p['url'] = "http://www.israelviana.es/post.php?id=" . $p['id'];
$posts[] = $p;
}

tpl_asignar("titulo", "Blog de Israel Viana");
tpl_asignar("posts", $posts);

tpl_cargar("inicio.tpl.php");

?>

Repetir que este sistema es solo una prueba de concepto. Si quieres un sistema de plantillas PHP serio para tu proyecto, te recomiendo PHPTemplate.

PHPTAL, un digno competidor de Smarty

Hoy he descubierto un sistema de plantillas para PHP que no conocía: PHPTAL. Está basado en TAL (Template Attribute Language), la sintaxis que utiliza ZPT, el sistema de plantillas de Zope, del que ya hemos hablado (si tuviese tiempo para aprender Python, me metería a saco con este maravilloso framework).

Este sistema de plantillas, a diferencia de Smarty (que vengo usando hasta ahora) está orientado a XML-XHTML. Qué mejor que un ejemplo:

Con TAL

<tal:block metal:define-macro="bloc">
 <div class="pagination" tal:condition="php: isset(pagination)">
 <tal:block repeat="item pagination">
  <tal:block condition="php: !empty(item.link)">

   <a tal:attributes="href item/link | ''" tal:content="item/label"/>
  </tal:block>
  <tal:block condition="php: empty(item.link)" content="item/label"/>

 </tal:block>
 </div>
</tal:block>

Con Smarty

<div class="pagination">
{foreach from=$pagination item=page}
 {if $page.lien != ""}
  <a href="{$page.lien}">{$page.label}</a>

 {else}
  {$page.label}
 {/if}
{/foreach}
</div>

A simple vista, se ve la diferencia sintáctica que ya he mencionado: la orientación a XML de TAL no permite generar CSS, por ejemplo, como sí permite Smarty. No obstante, esta limitación puede ser una gran ventaja, ya que con TAL es más fácil generar XHTML válido y trabajar con herramientas de autor (Dreamweaver, etc). Además, ofrece ciertas características de seguridad como validación de contenido inapropiado en los valores (inyección de JavaScript, etc). Y seguramente la sintaxis TAL permita transportar plantillas entre Zope y PHP con facilidad (aún no lo he comprobado in situ). También hay varios motores TAL para Perl, Java y Python (sin Zope), según la documentación de ZPT.

Otra de las ventajas (bueno, subjetivamente) de TAL es su orientación a objetos, bastante limitada en Smarty (por ejemplo, no soporta clases estáticas).

Los defensores de PHPTAL destacan la calidad del compilador en comparación con Smarty, que dicen poco adecuado a las últimas versiones de PHP.

Desde luego PHPTAL se presenta como una alternativa realmente digna, y bastante adecuada a la metodología orientada a objetos que estoy siguiendo en el patrón multivista (prometo publicar la segunda parte en breve). Como contrapartida, el no poder trabajar con documentos no XML. Zope lo soluciona incluyendo otro motor de plantillas llamado DTML, que pese a su sugerente nombre no es orientado a XML/HTML. Aunque personalmente no me parece una solución óptima para muchos proyectos meter un motor de plantillas más para, seguramente, CSS y JavaScript sólamente.

Patrones de desarrollo web: Multivista

Nota: el tratamiento que se le da en este artículo al término “vista” no es el mismo que el que se le da en el patrón MVC.

Hoy en día, muchos sitios web ofrecen la posibilidad de ver una página no sólo en HTML, sino en PDF, RTF y RSS. Y los webmasters que se quieren subir al carro de la web semántica ofrecen también la información en formato RDF.

Desde el punto de vista del HTML, el navegador interpreta las etiquetas <link rel="alternate"> como una vista alternativa del contenido (habitualmente RSS). Desgraciadamente no se usa todo lo que se debería (por ejemplo, las vistas en PDF y RTF que ofrece Joomla! No tienen su link rel, sino un enlace normal en la página). Pero ¿y por dentro del sitio web, en el CMS, cómo se implementa de una forma ordenada este modelo multivista? Propongo dos modelos, compatibles entre sí: el primero, más sencillo, basado en los motores de plantillas, y el segundo, basado en la programación orientada a objetos. Este segundo modelo se puede apoyar también en motores de plantillas, y ofrece, además, la posibilidad de implementar programación personalizada para cada plantilla. Vamos a verlo detenidamente.

Introducción a los motores de plantillas

En la creación de sitios web con motores de plantillas (como por ejemplo Smarty), la idea es separar el código (en nuestro caso PHP) de la interfaz (casi siempre HTML). La manera de usar estos sistemas de plantillas es sencilla: a través de código se extraen (de la base de datos, la entrada del usuario, etc) y manipulan los datos. Los datos se pasan de la forma más atómica posible a la plantilla, y ésta es un documento HTML con una sintaxis especial que permite imprimir el valor de esas variables e implementar una lógica sencilla.

Por ejemplo, este blog se apoya sobre Smarty. El script index.php extrae de la base de datos los últimos post y guarda los datos en un array multidimensional, de la forma:

$posts = array(

[0] = array (    "titulo" =>   "¿Te entiende Google? Web semántica y PLN",
"texto" =>    "<p>A Google no le interesa entenderme...",
"fecha" =>    "03-02-2009 12:05:30"),

[1] = array (    "titulo" => "La crisis de la web 2.0",
"texto" =>  "<p>Pensaba escribir un post sobre la crisis...",
"fecha" =>  "31-12-2008 16:55:28")
);

$smarty->assign("posts", $posts); //Pasa a la plantilla la variable $posts
$smarty->display("index.tpl"); //Invoca al motor de Smarty para que renderice la plantilla

Como podrás suponer, el código para obtener el array es
bastante sencillo, ya que la base de datos almacena la información en
un formato muy similar (tablas relacionales). Una vez obtenido, se le pasa la variable $post a la plantilla y se renderiza. La plantilla permite “programar” bucles para que recorra este array de posts, dándole formato a cada uno:

{foreach $posts as $p}
<div id="post">
<h1>{$post.titulo}</h1>
<h3>Publicado a las {$post.fecha} por Isra</h3>
{$post.texto}
</div>
{/foreach}

No es complicado entender la idea. Pongamos por ejemplo que en el blog tengo 4 páginas: index (el índice, que muestra los últimos posts), post (muestra un posts en concreto y sus comentarios), tag (últimos etiquetados con una determinada tag) y archivo (todos los posts de un mes concreto). Por tanto, tendré 4
scripts y 4 plantillas.

Una vista por cada formato

Pues bien, el patrón Multivista propone tener un conjunto de las plantillas (en este caso
index, post, tag y archivo) por cada vista que queramos implementar: HTML, RSS, RDF. En el caso del formato PDF no podríamos implementarlo con este sistema, ya que Smarty trabaja sobre texto
plano. Para ello usaremos el segundo modelo (orientado a objetos).

Una buena manera de implementar esto es, en vez de tener un directorio “templates” con las cuatro plantillas, crear subdirectorios en templates para cada vista, que contuviesen las plantillas:

Antes

  • templates/index.tpl
  • templates/post.tpl
  • templates/tag.tpl
  • templates/archivo.tpl

Después

  • templates/html/index.tpl
  • templates/html/post.tpl
  • templates/html/tag.tpl
  • templates/html/archivo.tpl
  • templates/rss/index.tpl
  • templates/rss/post.tpl
  • templates/rss/tag.tpl
  • templates/rss/archivo.tpl
  • templates/rdf/index.tpl
  • templates/rdf/post.tpl
  • templates/rdf/tag.tpl
  • templates/rdf/archivo.tpl

Una de las vistas podríamos llamarla “por defecto” (HTML), que se mostraría si una de las plantillas
de la vista requerida faltase. Desde el punto de vista del usuario,
podemos darle la oportunidad de elegir qué vista prefiere, a través
de la URL:

  • http://www.israelviana.es/blog/index.php?vista=html
  • http://www.israelviana.es/blog/index.php?vista=rss
  • http://www.israelviana.es/blog/index.php?vista=rdf

Por supuesto, el usuario no tiene que escribir manualmente la URL, sino se pueden dar enlaces para cambiar
de vista. [img de joomla o plone]

En los tres ejemplos de vista que hemos puesto, todos los formatos tienen la capacidad de enlazarse entre sí: HTML con las etiquetas <link rel="alternate"> que ya hemos comentado al comienzo,
RSS con los elementos link de channel y en RDF con rdf:about y otras propiedades.

Este sistema ofrece una metodología sencilla, implementable sin grandes cambios en el CMS y fácilmente
escalable.

El segundo método que veremos, más complejo y potente, lo dejamos para otro día ;-)