Blog de Israel Viana

Artículos sobre patrones

El patrón Strategy y las llamadas dinámicas en PHP

15 de junio de 2009 | 2 comentarios

Sigo explorando las novedades de PHP 5.3 y, entre ellas están las llamadas dinámicas a métodos estáticos, según he visto en esta interesante presentación de Ilia Alshanetsky. Veamos un ejemplo:

class Aplicacion {

	public static function getVersion() {
		return "1.2";
	}

}

$metodo = "getVersion";

Aplicacion::$metodo(); //Devuelve "1.2"

Se ve claramente que podemos llamar a un método estático cuyo nombre no definimos al escribir el código, sino que está en una variable. Esta característica ya estaba disponible en versiones anteriores de la rama PHP 5, tanto para clases como para métodos, pero no estáticos:

class Conexion {

	public function conectar($servidor, $usuario, $clave) {
		echo "Mira Mamá, me estoy conectando!";
	}

}

$clase = "Conexion";
$método = "conectar";

$objeto = new $clase();
$objeto->$método("localhost", "pepito", "grill0");
//Imprimirá "Mira Mamá, me estoy conectando!"

Bueno, y esta característica parece interesante, pero ¿cómo podemos aplicarla a la vida real? Uno de los ejemplos más claros que se me ocurre es la implementación del patrón de diseño Strategy. En este sencillo patrón se trata de cargar una u otra implementación de una clase dependiendo de alguna variable o acción del usuario. Por ejemplo, imaginemos que queremos conectarnos a bases de datos Oracle o MySQL, según qué servicio escoja el usuario desde un formulario. Primero definimos una clase abstracta Conexion que declare los métodos que las implementaciones han de desarrollar, y escribimos sendas clases hijas para Oracle y MySQL:

abstract class Conexion {

	private $recurso;

	public function conectar($usuario, $clave) { }
}


class ConexionOracle {

	public function conectar($usuario, $clave) {
		$this->recurso = oci_connect($usuario, $clave);
	}

}

class ConexionMysql {

	public function conectar($usuario, $clave) {
		$this->recurso = mysql_connect("localhost", $usuario, $clave);
	}

}

/*
 * $_POST['dbms'] proviene de un cuadro desplegable (option select) en que se
 * da a escoger entre oracle y mysql.
 * ucfirst() pone en mayúscula la primera letra
 */

$conector = "Conexion" . ucfirst($_POST['dbms']);
$instancia = new $conector;

try {
	$instancia->conectar($_POST['usuario'], $_POST['clave']);
} catch (Exception $e) {
	echo "Lo siento, pero por algún motivo la conexión ha fallado";
}

En fin, esto es todo, creo que es evidente la potencia de esta característica del lenguaje, que viene a intentar evitar el uso de eval() y dar al lenguaje un poco más de elegancia, al estilo del class.forName() de Java. Por cierto, para una descripción más profunda del patrón Strategy te recomiendo este interesante artículo de Jack Herrington sobre patrones implementados en PHP.


Patrones de desarrollo web: componentes HTML reutilizables

2 de junio de 2009 | 0 comentarios

Cuando construimos aplicaciones con muchos formularios u otras secciones con fragmentos repetitivos, a veces utilizamos componentes, como puede ser un cuadro desplegable (combo box u option select, llámale como quieras) para elegir una ciudad. Ya que las páginas se generan en el servidor (PHP, Java, Ruby...), se han inventado diversos artificios para reutilizar estos componentes: simples funciones o variables que devuelven el código HTML deseado, mini-plantillas (Smarty), elements de CakePHP, etc.

Pero ¿y si no estamos generando esos formularios en el servidor, sino dinámicamente en JavaScript? La solución en el cliente es disponer de componentes de diseño como fragmentos de HTML invocables por AJAX. Así de sencillo.

Por ejemplo, imaginemos que nuestra aplicación/web tiene muchos formularios en los que elegir una ciudad en un control option select. Si estamos generando formularios a través de JavaScript, sería muy útil disponer de una URL invocable a través de AJAX que nos devolviese algo como

<select>
	<option value="1">Vigo</option>
	<option value="2">Murcia</option>
	<option value="3">Sevilla</option>
</select>

Y si agregamos algunas opciones...

<?php $opciones = array("Vigo", "Murcia", "Sevilla") ?>
<select name="<?php echo $_GET['nombre'] ?>">
<?php foreach ($opciones as $n=>$nombre) { ?>
	<option value="<?php echo $n ?>" <?php if ($_GET['valor'] == $n) echo "selected=\"\"" ?>><?php echo $nombre ?></option>
<?php } ?>
</select>

Entonces tenemos algo como form_ciudades.php?nombre=xxx&valor=yyy que nos devolverá un option select de nombre xxx con el valor yyy seleccionado por defecto.

Si combinamos esto con alguna función que envuelva peticiones AJAX y nos devuelva el contenido requerido de forma sencilla, conseguiremos un código fácilmente legible. Aquí va un ejemplo real, que estoy utilizando para una práctica de la universidad:

$("#detalles > table").html(
	   "<tr><td>Aerolínea:</td><td>" 
	   + ajax("sn_aerolineas.php?nombre=aerolinea&valor=1") 
	   + "</td></tr>"
	   + "<tr><td>Origen:</td><td>" 
	   + ajax("sn_localidades.php?nombre=origen&valor=1") 
	   + "</td></tr>"
	   + "<tr><td>Destino:</td><td>" 
	   + ajax("sn_localidades.php?nombre=destino&valor=1") 
	   + "</td></tr>");

Para quien no conozca jQuery, la función $("#detalles > table") devuelve la tabla que hay dentro del elemento del contenedor "detalles", y la función html() cambiará su innerHTML. ajax es una pequeña y sucia función que me devuelve directamente una respuesta AJAX.

Este patrón se puede aplicar para muchos más casos, como selectores de fecha (input + calendario), o incluso llamadas que devuelvan datos mucho más pequeños, como la foto de un usuario o una lista de categorías. Pero recuerda que este patrón debe ser aplicado sólamente cuando se generan elementos dinámicamente. Es decir, usa AJAX sólo cuando sea necesario para mejorar la usabilidad y/o el rendimiento de tu sitio.


Patrones de desarrollo web: Multivista (II)

14 de mayo de 2009 | 0 comentarios

Aquí tienes la procrastinada segunda parte del patrón Multivista. En este post veremos cómo añadir lógica a las vistas y cuáles son los límites de este patrón. Como caso práctico del Multivista orientado a objetos tendremos este mismo blog.

¿Cuándo es necesario este segundo método?

  • Cuando hay plantillas sin salida de texto: PDF, imágenes, flash...
  • Cuando se utilizan helpers o librerías para generar documentos: Pear-Html, DOMDocument, etc
  • Cuando no se quiere emplear un sistema de plantillas.

El patrón Multivista orientado a objetos es similar al patrón Template Two Step View, y se introduce normalmente en una estructura Modelo-Vista-Control de tres capas, como ya expliqué en la primera entrega. Se basa principalmente en la abstracción de las acciones ("postergar los problemas", como dice mi compañero Dors). Si en el primer método cargábamos los mismos datos introduciéndolos en una u otra plantilla, ahora tendremos una abstracción de tres niveles, donde podremos cargar unos datos comunes (ahí suelen estar las llamadas a los DAO) en el primero, ejecutar unas acciones específicas en el segundo, y el tercer nivel (opcional) serían las plantillas. Veámoslo con un ejemplo:


class VerPost extends Vista {
    public function cargarDatos() {
        Dao::getPost($_GET['id']);
    }

    public post() {
        //Aquí se opera con los parámetros post, si los hubiese (por ejemplo, registrar un comentario)
    }
}

class VerPostHtml extends VerPost {
    //Esta vista podría haber sido implementada con el 1º método
    public function run() {
        $smarty->assign("post", $this->cargarDatos());
        $smarty->display("verPost.tpl");
    }
}

class VerPostPdf extends VerPost {
    //Esta vista requiere código PHP específico, por lo que el 1º método no valdría
    public function run() {
        header("Content-type: application/pdf");
        $datos = $this->cargarDatos());
        $pdf = new Pdf();
        $pdf->addText($datos['titulo']);
        echo $pdf->flush();
    }
}

No es complicado entender este método: la parte de la vista se segrega en dos niveles, el primero es una clase común a todas las vistas del módulo y el segundo nivel son las vistas específicas. Pero el Multivista no se queda en este sencillo código. Podemos establecer clases horizontales comunes a todas las vistas específicas de un tipo completo, por ejemplo, las vistas en PDF:


class ComunPdf {
    static public function run() {
        //Construye una cabecera común a todas las vistas
        header("Content-type: application/pdf");
        $pdf = new Pdf();
        $pdf->addText("Blog de Israel Viana");
        $pdf->addText("www.israelviana.es");
    }
}

class ComunRss {
    static public function run() {
        header("Content-Type: application/xml+rss");
    }
}

Clases comunes a vistas del mismo tipo

Quizás queramos tener alguna funcionalidad común para todas las vistas RSS, por ejemplo. Esta funcionalidad se puede implementar de varias maneras: por una parte las propias clases específicas pueden llamar a las transversales:


class VerPostPdf extends VerPost {
    public function run() {
        $datos = $this->cargarDatos());
        VistaPdf::cabecera();
        $pdf->addText($datos['titulo']);
        echo $pdf->flush();
        VistaPdf::pie();
    }
}

O bien puede hacerse desde el motor de vistas:


//Instancia la clase que corresponda
$vista = $_GET['vista'];
$plantilla = $_GET['plantilla'];
eval("$v = new $vista$plantilla;");

//Invoca los métodos comunes a la plantilla
eval("Comun$plantilla::run();");

//Ejecuta la plantilla
$v->run();

URL amigables

Siendo ortodoxos, esto no estaría dentro del patrón Multivista en sí, pero puede ser una buena forma de manejar las aplicaciones que funcionen con este patrón: veamos cómo interpretar URL del tipo: http://servidor.net/Accion/Vista/param1/param2/.../paramn/vista. Manejar direcciones con esta estructura tiene varias ventajas:

  • Organizar nuestros módulos de una manera bastante intuitiva, separando los scripts "importantes" para nuestra aplicación y las librerías
  • Poder disponer de un script único de acceso (el dispatcher de URL), que maneje los argumentos, los errores y los ataques de inyección SQL y por el estilo. Es decir, se solicite la URL que se solicite, siempre se ejecutará el dispatcher.
  • Cumplir la recomendación de accesibilidad de Jakob Nielsen de hacer URL "hackeables", para que un usuario pueda cambiar de plantilla sin necesidad de enlaces.
  • Mejorar el posicionamiento en buscadores de nuestro sitio web (SEO).

Lo primero es redirigir todas las peticiones al dispatcher. En Apache se hace así:

RewriteEngine on
RewriteRule .* index.php

Finalmente, el código del dispatcher tendría esta pinta (el código está bastante simplificado, la versión real la publicaré con el resto del código del blog):


//Vistas
$vistas = array(
    'Index'     => array('nombre'=>"Index",  'plantillas'=>array("Html", "Rss", "Rdf")), 
    'Post'      => array('nombre'=>"Post",       'plantillas'=>array("Html", "Rss", "Rdf")), 
    'Archivo'   => array('nombre'=>"Archivo",    'plantillas'=>array("Html", "Rss", "Rdf")), 
    'Pagina'    => array('nombre'=>"Pagina",     'plantillas'=>array("Html", "Rss", "Rdf")),
    'Proyectos' => array('nombre'=>"Proyectos", 'plantillas'=>array("Html"))
);

$GLOBALS['vista_por_defecto'] = "Index";
$GLOBALS['plantilla_por_defecto'] = "Html";

$peticion = $_SERVER['REQUEST_URI'];
$parametros = split('/', substr($peticion, strlen(RUTA_R))); //Quita la URL base

//Obtiene el módulo
$v = (strlen($parametros[0])) ? ucfirst($parametros[0]) : $GLOBALS['vista_por_defecto'];

//El módulo especificado no existe
if (!array_key_exists($parametros[0], $vistas)) {
    include("404.php");
    die();
}

//Elimina el módulo de los parámetros
array_shift($parametros);

//Protege contra el error de no incluir barra final en URL que no contienen vista específica, por ejemplo /Post/31/titulo-del-post
$ultimo_parametro = ucfirst($parametros[count($parametros)-1]);
if (array_search($ultimo_parametro, array_merge(array(null), $vistas[$v]['plantillas']))) {
    $p = $ultimo_parametro;
    array_pop($parametros);
} else {
    $p = $GLOBALS['plantilla_por_defecto'];
}

//Verifica si está existe la vista
if (array_key_exists($v, $vistas)) {
    $vista = $vistas[$v];
    //Verifica si existe la plantilla
    //No vale negar con ! porque si el índice es 0, la condición no se cumpliría
    if (null == array_search($p, array_merge(array(null), $vista['plantillas']))) die("La plantilla especificada no existe");
} else {
    die("La sección especificada $v no existe");
}

//Pasa los parámetros a la vista
$GLOBALS['parametros'] = $parametros;
$smarty->assign("parametros", $GLOBALS['parametros']);

$GLOBALS['vista_actual'] = $v;
$GLOBALS['plantilla_actual'] = $p;

//Carga las clases vista y plantilla
require_once("vistas/Vista$v.php");
require_once("vistas/$v/$v$p.php");

//Instancia la plantilla
eval("\$vista = new $v$p();");
//Métodos comunes (transversal) al tipo de plantilla
if (file_exists("vistas/Plantilla$p.php")) include("vistas/Plantilla$p.php");
//Ejecuta la plantilla
$vista->run();

Y con esto, un DAO y poco más (veremos en sucesivos artículos qué es lo que falta) tendremos un marco de trabajo para aplicaciones web multivista. No se trataría de un MVC en toda regla, pero sí de una arquitectura de tres capas válida para aplicaciones robustas.


Patrones de desarrollo web: Multivista

12 de febrero de 2009 | 2 comentarios

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 el elementos linkchannel 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 ;-)


israelviana.es es propiedad de Israel Viana, escrito en Murcia (España). Puedes ponerte en contacto conmigo a través de la dirección de e-mail .com.
Información en RDF Metadatos Dublin Core Creative Commons License