Blog de Israel Viana

Artículos sobre programación

El ataque de eu2010.es en otra web de la administración

12 de enero de 2010 | 3 comentarios

Mucho revuelo se ha armado con el ataque a la web de la presidencia europea de turno. Llegó incluso a estar en la portada de El Mundo (edición impresa).

Por su parte, desde la Secretaría de Estado de Comunicación reiteraron que la página no ha sido alterada por ningún intruso, ya que la imagen mostrada en el engaño ha sido cargada desde un servidor remoto y no es posible acceder a ella usando normalmente la web.

Y la verdad es que, aunque 11,9 millones por adaptar un OpenCMS y darle mantenimiento y "seguridad" es el robo del siglo, hay que admitir que el error era del propio OpenCMS (el gestor de contenidos que utilizan... que no es PHP :-P), y además un XSS, si no se utiliza para robar cookies o hijacking es prácticamente inofensivo.

No obstante, este error de principiante no es el único pecado de Telefónica (o de la cárnica que haya subcontratado para el proyecto). Santi Saez nos desvela que la web fue alojada en un servidor con un sistema operativo obsoleto (Debian 4), el panel de control Plesk accesible públicamente, el SSH accesible públicamente y permitiendo el login a root y un BIND (servidor de DNS) vulnerable. Además, el servidor de nombres no era dedicado. Ahora han intentado arreglarlo externalizando el hosting en Akamai.

Pero no nos desviemos del tema, que es explicar cómo funciona esta vulnerabilidad, y cómo explotarla en otra web de la administración que todavía no ha parcheado el OpenCMS.

Qué es XSS

Un ataque XSS o cross-site scripting es una vulnerabilidad de aplicaciones web que consiste básicamente en conseguir que en la página víctima se cargue un script de otro sitio. Como sabemos que los scripts (casi siempre JavaScript) se ejecutan en el navegador del cliente, es evidente que este ataque no va a alterar el servidor en nada.

Pero ¿cómo hacer que una página cargue un script? Para buscar vulnerabilidades XSS y cualquier otra vulnerabilidad web es esencial que encontrar puntos de entrada de datos por parte del usuario. Estas puertas por las que introducimos datos suelen ser:

  • URL (casi siempre parámetros): la dirección de una página la introducimos nosotros y la interpreta el servidor. Habitualmente suelen dar más juego los parámetros (en PHP, lo que se obtiene con $_GET[]). Es el que usaremos en nuestro caso práctico.
  • Formularios: en los formularios de login, búsqueda, etc se nos piden datos que después el servidor interpreta. Por aquí se suelen colar las inyecciones SQL, otro ataque que veremos en futuros posts y que puede llegar a ser muuuuy peligroso, a la vez que sencillo.
  • Cabeceras: son manejadas por los servidores. No suelen dar problemas, porque los desarrolladores web no suelen tocar ahí ;-)
  • Cookies: más complejas y difíciles de atacar, aunque pueden llegar a ser también muy peligrosas (autenticación falsa, etc). Es un tema interesante para un futuro post.

Pues bien, entremos en materia. Imagina un formulario de búsqueda de una web cualquiera. Hay un cuadro de texto en el que tú introduces una palabra, por ejemplo, 'normativas calidad' porque quieres buscar las normativas de calidad de un organismo público. Cuando pulsas "buscar", aparece una página de resultados en el que arriba dice "se han encontrado x resultados para 'normativas calidad'". Es decir, repite lo que le has dicho. Ha puesto en la página lo que tú habías introducido en el cajetín de búsqueda.

Vale, pero ¿y si en lugar de 'normativas calidad' pones '<img src="http://bean.com/mister-bean.jpg">'? En teoría la página debe repetir lo que has escrito. Si la web no está protegida, entonces verás una linda foto de Míster Bean en la página de resultados, ya que el navegador interpreta el HTML que has introducido y el buscador ha repetido. Acabas de inyectar HTML, pero no has manipulado el servidor. Esa foto de Míster Bean la ves tú en tu navegador y nadie más.

Si en lugar de HTML plano introduces un script (bien por invocación bien por introducción directa del código), ese script se ejecutará en el navegador, y podrás manipular los elementos de la página, hacer redirecciones... lo que quieras, pero siempre sabiendo que eso se está ocurriendo en tu navegador y sólo en tu navegador.

En los formularios de búsqueda —como ocurrió con eu2010.es— esto no constituye un peligro real para la página (aunque si el sitio es famoso o asociado a política no te dará muy buena imagen), pero si esta inyección la introduces en un cuadro de texto para publicación (un foro, un comentario en un blog, etc), entonces las manipulaciones que hagas las verán todos los que accedan a esa página, y eso ya empieza a ser peligroso.

Tutorial para ser hacker en 5 minutos

Vamos a ver este ataque en la práctica. La web del Centro de Investigaciones Sociológicas, construida sobre OpenCMS, tiene un lindo buscador. Si introducimos una palabra clave como "hola mundo", la página de resultados repetirá nuestras palabras como un loro:

Página de resultados de búsqueda

Aquí tenemos el código fuente:

<input class="txt" type="text" size="15" name="query" value=hola mundo >

Primer fallo... no poner comillas para los valores, como exige el estándar HTML. Bueno, más fácil nos lo ponen.

Si en lugar de 'hola mundo' ponemos '>hola mundo <', entonces el código generado será

<input class="txt" type="text" size="15" name="query" value=>hola mundo <>

Lo que acabamos de hacer es inyectar código cerrando la tag input con el símbolo '>', y el resultado será que vemos el texto box de búsqueda vacío y nuestro mensaje, 'hola mundo' a su lado. Bien, vamos a divertirnos. Si introducimos '><script>alert("Hola mundo")</script>' inyectamos HTML con un JavaScript (la función alert() muestra un mensaje emergente), que se ejecuta con el siguiente resultado:

XSS con un alert() en JavaScript

Hala, ya somos super-hackers de la CIA —voy corriendo a publicarlo en Meneame :-P—. Ah, no, todavía nos falta el "golpe final", introducir la imagen de Mr. Bean. Ahora simplemente inyectamos una imagen (habiendo cerrado previamente la tag input con el signo '>'):


><img src="http://blog.tmcnet.com/blog/tom-keating/images/mr-bean.jpg">

Mr. Bean en la web del CIS

Si aparece repetida es porque en la página de resultados se muestra el mensaje de "buscar otra vez" en varios idiomas.

(Lo que viene ahora no es muy importante): hay una particularidad en este caso, y es que, nuestra inyección la podemos hacer tanto con la casilla de búsqueda (es decir, enviando el formulario por POST) como introduciendo el parámetro query —el nombre del campo de texto— en la propia URL (es decir, haciendo petición GET). Esto ocurre porque los servlets—eso que se utiliza en Java para hacer aplicaciones web— mete en el mismo "saco" (el método getParameter()) los parámetros introducidos por formularios y URL. Eso, combinado con la mala práctica de poner doPost en doGet y viceversa —o sea, decirle a Java que haga lo mismo para las peticiones GET y POST— hace que podamos inyectar código no sólo a través del campo de texto de búsqueda, sino también desde la URL. Así podremos distribuir nuestro super-hackeo a nuestros amigos, y al igual que pasó con eu2010.es, decir/tuitear...

¡Mira, mira! ¡Mr. Bean también se fue al CIS! http://is.gd/679lU

¿Cómo prevenir esta vulnerabilidad?

Si eres webmaster o desarrollador, ve y corre a comprobar si tus webs son vulnerables a este ataque. En caso de que hayas recibido una desagradable sorpresa, no tranquilizará saber que prevenirlo es tan sencillo como filtrar con una función la entrada del usuario. Imaginemos que tenemos un buscador en PHP que recibe las palabras clave por un formulario y un campo de texto llamado "busqueda". Si en nuestra página de resultados tenemos algo como

Se han encontrado <?php echo $numero_de_resultados ?> resultados 
para <?php echo $_POST['busqueda'] ?>

sólo tendremos que sustituirlo por

Se han encontrado <?php echo $numero_de_resultados ?> resultados 
para <?php echo htmlentities($_POST['busqueda']) ?>

...y estaremos a salvo (más información sobre htmlentities()). Aunque hay métodos más sofisticados y completos, este funciona muy bien, ya que sustituye los caracteres especiales de HTML en sus entidades correspondientes, de tal forma que el atacante no verá su código interpretado, sino mostrado tal y como lo escribió.

Moraleja

¡No te fíes de los usuarios! Filtra todas las entradas (¡todas! formularios, URL, etc). Si esperas un valor numérico en un campo de texto, comprueba que es un campo de texto, si esperas una dirección de email, comprueba que es un email (pero con expresiones regulares ¿eh? nada de cosas raras).

Conclusión

Vivimos en la era del ladrillo, y en contra de lo que nuestros torpes políticos digan, el modelo de negocio del ladrillo no sólo no ha muerto, sino que se ha extendido como un cáncer hacia la industria informática. Nuestro negocio está lleno de gente con talento que cobra poco, y políticos corruptos que chupan generosas subvenciones y concursos públicos de una administración a la que no le importa malgastar un dinero que nos pertenece a todos.

Como dice un amigo... en España la ingeniería informática tiene tres principales salidas: por tierra, mar y aire.


Introducción visual a jQuery

20 de diciembre de 2009 | 2 comentarios

Hace unos días impartí un breve taller de introducción a jQuery a mis amigos del SAW. El taller está pensado para durar unas cuatro o cinco horas, con ejemplos prácticos y casos reales, de forma que el asistente salga conociendo las posibilidades de jQuery y su aplicación en la vida real. Aquí tienes la presentación visual que complementa el taller (hecha con Prezi):

El taller está orientado a diseñadores web y programadores con conocimientos básicos de JavaScript y HTML. Así que si tú y tu equipo queréis aprender jQuery en un día, decidle a vuestro jefe que me contrate ;-)


5 razones para no usar Smarty (o similares)

21 de noviembre de 2009 | 9 comentarios

En 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):

Algunos expertos han escrito artículos en profundidad sobre este tema, por ejemplo "Template Engines" de Brian Lozer o la entrada de "PHP and Templates" de phpPatterns.

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.”

Aún más rendimiento con CssDispatcher

25 de octubre de 2009 | 0 comentarios
CssDispatcher

En el artículo "Maneja las CSS como un profesional" veíamos algunas técnicas para insertar variables y funciones en las hojas de estilo, así como posibles mejoras del rendimiento en el cliente a la hora de servir las CSS, y para ello utilizábamos la biblioteca CssDispatcher.

Pues bien, en este post vamos a ver varias soluciones para aumentar el rendimiento en el servidor cuando servimos hojas de estilos, especialmente si son hojas de estilos tratadas con CssDispatcher.

Es importante diferenciar las mejoras de rendimiento en el cliente y el servidor. Mientras los benchmarks nos dicen que el 80% del tiempo de carga de una página lo causa el cliente (transferencias, renderizado, etc), para nosotros también es importante ese otro 20%, porque genera una carga en el servidor (acceso a disco duro, procesador, memoria...) que puede ponernos en aprietos si las visitas aumentan.

Accesos al disco duro

De por sí, servir una hoja de estilos simple no suele ser muy costoso para un servidor web. Si un cliente solicita /main.css, el servidor abre el archivo, lo lee y lo devuelve. Además, si el sistema operativo implementa caché de ficheros, 1000 visitas no generarán 1000 accesos al disco duro, lo cual aumentará la velocidad y reducirá la carga del servidor.

Si queremos ir más allá y acelerar aún más los accesos a disco duro podemos poner los archivos del servidor un sistema de archivos montado sobre la memoria RAM. Es decir, una porción del sistema de archivos que, en lugar de guardarse en el disco duro, lo hace sobre una porción de la memoria RAM. No es una técnica muy recomendable, porque aunque no es difícil montarlo, los datos desaparecen al apagar o reiniciar la máquina, por lo que habría que tener sincronizados los directorios en memoria con otros en el disco duro para que no se perdiesen.

Procesamiento de la petición

Como ya he dicho antes y todo el mundo sabe, para el servidor web devolver un archivo estático no le cuesta más que abrirlo e irlo enviando por el socket. Pero si la hoja de estilos pasa por el compilador de PHP (por introducir códigos PHP en la hoja de estilos) entonces la cosa se complica un poco. Para acelerar el procesado de scripts es recomendable usar:

  • Una caché de salida, que evita tener que ejecutar repetitivamente el mismo código para cada petición.
  • Un opcode cache, que evita tener que compilar el código para cada petición (si hemos implementado caché de salida, para la mayoría de las peticiones no será necesario ni compilar ni ejecutar el código).
Diagramas de flujo para el despacho de peticiones con caché

Para la opcode cache existen herramientas como APC, XCache o eAccelerator, que una vez instaladas y configuradas debidamente funcionan sin que tengamos que modificar el código de nuestra aplicación web. Puedes leer más sobre opcode cache en PHP Accelerators.

En cuanto a la caché de salida, hay principalmente dos caminos a seguir: gestionar la caché desde el servidor web o desde PHP. La ventaja del primero es que, al no ejecutar código PHP es un poco más rápido. Esto se puede lograr con el módulo Apache mod_cache. La otra posibilidad, gestionarlo con PHP, nos da más flexibilidad y control sobre la solución. En concreto, podremos guardar la caché en el disco duro o en memoria, con gestores de caché como Memcached (mi preferido).

Caché con URL propia

Existe una tercera solución, y es utilizar una caché en disco duro sin procesar las peticiones por PHP. El funcionamiento es el siguiente: si tenemos una hoja de estilos generada con CssDispatcher en /estilos.css.php, podemos guardar la salida en un archivo y ponerlo accesible en /estilos.css. Al ser un archivo estático, se servirá tan rápido como una hoja de estilos normal. Este sistema tiene un problema, y es que si tenemos hojas de estilos específicas para navegadores, no resultará fácil servir una u otra de forma estática. La única solución aparente es utilizar redirecciones y estructuras condicionales en los ficheros de configuración de Apache.

Algo de código, por favor

Vamos a implementar tres sistemas de caché: uno con Memcached, otro en disco y otro en disco accesible directamente.

Cache en Memcache

<?php

include 'class.Css.php';

//Nombre del elemento en Memcache
$cache_name = 'moduleName_' . Css::getUserAgent();
//Tiempo de vida. Para archivos que no cambien mucho, poner valores altos
$cache_time = 3600*24; 

//Conexión a Memcache
$mc = new Memcache();
$mc->connect('localhost');

//Si la salida no está en caché, se genera y se almacena
if (!$out = $mc->get($cache_name)) {

    //Incluye la biblioteca CssDispatcher.
    //No se incluye si no es necesaria
    include 'class.CssDispatcher.php';

    $estilos = new CssDispatcher;

    //Crea una nueva hoja de estilos
    $general = new Css('example.css.php');
    //Asigna variables
    $general->background = '#eee';
    $general->border_color = 'red';
    $general->header_size = 2.1;

    //Crea otra hoja de estilos para navegadores WebKit
    $another = new Css('example2.css.php', Css::UA_WEBKIT);
    $another->bold = 'font-weight: bold';

    //Añade las hojas de estilo al dispatcher
    $estilos->add($general);
    $estilos->add($another);

    //Añade un aviso
    $out = '/* Generated by CssDispatcher ' . strftime('%c') . " */\n"
        . $estilos->render(false, true, false, true);

    //Guarda la salida en Memcache
    $mc->set($cache_name, $out, null, $cache_time);
}

header("Content-Type: text/css");
echo $out;
die();

?>

Tiempo de ejecución (renovando la caché): 3.6480ms
Tiempo de ejecución (utilizando la caché): 0.6439ms

Caché en disco duro

Este script tendrá será más lento y pesado para la CPU que el anterior, pero no necesita de servicios adicionales como Memcached, imposibles de instalar en alojamientos compartidos.

<?php

include 'class.Css.php';

//Archivo de caché
$cache_name = 'cssCache/moduleName_' . Css::getUserAgent();
//Tiempo de vida, poner valores altos para archivos que no suelen cambiar
$cache_time = 30; 

if (file_exists($cache_name)) {
    if (date('U') - fileatime($cache_name) > $cache_time) {
        $regenerate = true;
    } else {
        $out = file_get_contents($cache_name);
    }
} else {
    $regenerate = true;
}

//Si la salida no está en caché, se regenera
if ($regenerate) {

    //Carga la biblioteca CssDispatcher
    include 'class.CssDispatcher.php';

    $estilos = new CssDispatcher;

    //Crea una nueva hoja de estilos
    $general = new Css('example.css.php');
    //Asigna variables
    $general->background = '#eee';
    $general->border_color = 'red';
    $general->header_size = 2.1;

    //Añade otra hoja de estilos para navegadores WebKit
    $another = new Css('example2.css.php', Css::UA_WEBKIT);
    $another->bold = 'font-weight: bold';

    //Añade las plantillas al dispatcher
    $estilos->add($general);
    $estilos->add($another);

    $out = '/* Generated by CssDispatcher ' . strftime('%c') . " */\n"
        . $estilos->render(false, true, false, true);

    //Graba la salida en el fichero
    file_put_contents($cache_name, $out);
}

header("Content-Type: text/css");
echo $out;
die();

?>

Tiempo de ejecución (renovando caché): 1.290ms
Tiempo de ejecución (utilizando caché): 0.242ms

El último caso, una caché en ficheros accesibles públicamente, es más rápida y más ligera que la anterior, pero nos añade la tarea de actualizar la caché cuando nos interese, bien con una tarea programada (cron) o mediante algún otro mecanismo.

<?php

include 'class.Css.php';
include 'class.CssDispatcher.php';

//Generamos hojas de estilos para todos los navegadores
$navegadores = array(Css::UA_IE6, Css::UA_IE, Css::UA_GECKO, Css::UA_WEBKIT);

//Para cada navegador generamos la hoja de estilos correspondiente
foreach ($navegadores as $navegador) {
    //Archivo de caché
    $cache_name = 'cssCache/estilos_' . $navegador . '.css';
    //Tiempo de vida. Para archivos que no cambien mucho, poner valores altos
    $cache_time = 30; 

    $estilos = new CssDispatcher;

    //Crea una nueva hoja de estilos
    $general = new Css('example.css.php');
    //Asigna variables
    $general->background = '#eee';
    $general->border_color = 'red';
    $general->header_size = 2.1;

    //Añade las plantilla al dispatcher
    $estilos->add($general);

    //Hoja de estilos para WebKit
    //No vale utilizar la detección de navegador de CssDispatcher
    if ($navegador == Css::UA_WEBKIT) {
        $another = new Css('example2.css.php');
        $another->bold = 'font-weight: bold';
        $estilos->add($another);
    }

    $out = '/* Generated by CssDispatcher ' . strftime('%c') . " */\n"
        . $estilos->render(false, true, false, true);

    //Graba la salida en el fichero
    file_put_contents($cache_name, $out);
}

echo "Caché de CSS actualizada";

?>

Tiempo de ejecución del actualizador de caché: 4.009ms
Tiempo de obtención de la hoja de estilos: 86ms (incluye la transmisión del fichero)

Ya que con este método no tenemos un sólo punto de entrada para las diferentes hojas de estilos, será necesario cargar una u otra CSS en función del navegador, a través de los artificios clásicos: comentarios condicionales o la cabecera User-Agent. Pero tendremos que hacerlo manualmente, ya que al usar ficheros estáticos no se ejecuta CssDispatcher. Por ejemplo, según el ejemplo, si el navegador es Firefox tendremos que invocar cssCache/estilos_20.css.

Por mi parte, es todo por hoy. ¿Conoces alguna otra técnica para aumentar el rendimiento al servir CSS? ¿Has probado a cachear la salida de CssDispatcher?


Linux es un buen entorno de programación en PHP

25 de septiembre de 2009 | 5 comentarios

Siempre he apostado por el software libre, pero hasta hace no mucho usaba Windows, un sistema privativo, para desarrollar. No me convencían las herramientas de programación en PHP para Linux, ya que estaba muy contento con Notepad++ para escribir scripts y TortoiseSVN para trabajar con Subversion. Usaba un servidor WAMP y phpMyAdmin para administrar la base de datos.

Ahora, sobre Debian, trabajo con unas herramientas que no tienen nada que envidiar a las anteriores. Vayamos por partes.

Un editor de PHP para Linux

Si algo me faltaba en Notepad++ era un autocompletado de código potente (el de Notepad++ se basa solamente en las palabras del archivo actual) y la autodocumentación en línea (es decir, soporte de phpDoc). Tras darle una oportunidad a Eclipse/PDT, me he decidido definitivamente por NetBeans. Tiene integración con Subversion, phpDoc, XDebug y Firefox, y me parece más productivo que Eclipse. Una de las características que más me gusta es que tiene en cuenta la cláusula @return de los comentarios phpDoc de una función para entender el tipo de dato que devuelve:

NetBeans, mi IDE favorito para PHP

No obstante, no he dejado de usar Notepad++, gracias a WINE. Lo utilizo para manejar archivos de diferentes codificaciones, procesar textos con expresiones regulares y escribir PHP cuando no me apetece iniciar NetBeans ;-)

Un cliente Subversion para Linux

Como ya he dicho, NetBeans tiene soporte nativo de Subversion. En otros casos, utilizo kdesvn y RapidSVN. Además, he aprendido el valioso placer del cliente svn por línea de comandos ;-) Por cierto, existe un cliente privativo aunque muy potente llamado SyncroSVN, disponible para Windows, Mac y Linux.

Un depurador de PHP para Linux

De nuevo NetBeans me soluciona la papeleta, pero también utilizo KCacheGrind, un excelente visor de volcados XDebug:

KCacheGrind visualiza los informes de XDebug de múltiples formas

Cross-browser en Linux

Si algo abunda en GNU/Linux son los navegadores. Los de siempre, Firefox (con el maravilloso Firebug), Opera, Chrome y lynx (o links o elinks), además de Konqueror (que nadie usa). Para el malvado Internet Explorer utilizo una máquina virtual con Windows XP en la que dispongo de muchas versiones de IE, gracias a Internet Explorer Collection. Además, existen renderizadores de Internet Explorer on-line.

Internet Explorer 1

Instalar extensiones es más fácil en Linux

Los paquetes Apache+PHP para Windows suelen traer algunas extensiones, pero a la hora de instalar otras nuevas (tanto PECL como PEAR) los métodos varían, y las utilidades de línea de comandos no siempre funcionan.

En Linux muchas extensiones PECL y bibliotecas PEAR están disponibles a través de los repositorios.

Extensiones PHP a través del repositorio

Además, el paquete de desarrollo php5-dev permite instalar compilando las extensiones de la forma más sencilla:

# pecl install nombre_de_la_extension

Y lo mismo para librerías PEAR:

# pear install nombre_de_la_biblioteca

Conclusión

Si aún trabajas con Windows, anímate a probar GNU/Linux con Ubuntu o la distribución que más te guste. En una hora habrás podido instalar todas las aplicaciones que necesitas para programar de forma productiva y cómoda con PHP. Si ya lo has hecho, ¿qué experiencia tienes? ¿utilizas alguna de las herramientas que he citado? Comentar es gratis ;-)


Una gran verdad

22 de septiembre de 2009 | 0 comentarios

La informática es la ciencia de cómo crear programas, no cómo crear ordenadores; y como los programas sirven para resolver los problemas de la gente, un informático debe principalmente entender a la gente.

Edsger Dijkstra (más citas geniales)


Spaghetti a la carbonara

22 de junio de 2009 | 2 comentarios

He aquí un maravilloso ejemplo de código espagueti que he encontrado en... bueno, mejor correr un es-tupido velo.

function revisar_email(correo) {
		var nombre_valido = false;
		var servidor_valido = false;
		var dominio_valido = false;
		var email = correo.value;
		if (email != ""){
			// se comprueba si ha metido algo en el campo
			if (email.indexOf('@') != -1){
				// se comprueba si contiene la @
				var array1 = email.split('@');
				if ((array1[0] != "") && (array1[1]) != ""){
					// se comprueba que tenga caracteres delante y detrás de la @
					if (array1[1].indexOf('.') != -1){
						// se comprueba si tiene . detrás de la @
						var array2 = array1[1].split('.');
						if ((array2[0] != "") && (array2[1]) != ""){
							// se comprueba si tiene 2 partes después de la arroba
							var nombre = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.";
							var dominio = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
							var i, j;
							for (i = 0; i <= array1[0].length; i++){
								if (nombre.indexOf(array1[0].charAt(i)) == -1){
									// revisamos que los caracteres del nombre sean correctos
									nombre_valido = false;
									break;
								}
								else{
									nombre_valido = true;
								}
							}
							for (i = 0; i <= array2[0].length; i++){
								if (nombre.indexOf(array2[0].charAt(i)) == -1){
									// revisamos que los caracteres del servidor sean correctos
									servidor_valido = false;
									break;
								}
								else{
									servidor_valido = true;
								}
							}
							if(array2[1].length > 1){
								// revisamos que la longitud del dominio sea mayor a 1
								for (i = 0; i <= array2[1].length; i++){
									if (dominio.indexOf(array2[1].charAt(i)) == -1){
										// revisamos que los caracteres del dominio sean correctos
										dominio_valido = false;
										break;
									}
									else{
										dominio_valido = true;
									}
								}
							}
						}
					}
				}
			}
			if ((nombre_valido == true) && (servidor_valido == true) && (dominio_valido == true)){
				// si todo es correcto devuelve true
				return true;
			}
			else{
				alert("El formato del email no es correcto.");
				return false;
			}
		}
	}

66 líneas para validar una dirección de email. Vaya, ¿y si usamos una expresión regular?

function revisar_email(correo) {
	var filtro = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
	if (!filtro.test(correo)) {
		alert('La dirección de correo electrónico no es correcta');
		return false;
	} else {
		return true;
	}
}

¡Hombre! ¡La misma funcionalidad implementada en 8 líneas! Moraleja:

  • El mal programador es malo en JavaScript, en Java y en cualquier otro lenguaje que toque.
  • Las expresiones regulares son nuestras amigas. Son feas y antipáticas, pero son nuestras amigas.
  • Dice Linus Torvalds que nunca deberían ser necesarios más de 3 niveles de anidamiento. Yo más bien pondría el límite en 5.
  • ¡Por Dios, no llames a tus variables array1 o array2! Utiliza nombres que tengan algún significado
  • Cuantas menos líneas de código, menos probabilidad de errores.
  • Cuantas menos líneas de código, más rápido será el algoritmo.
  • Cuantas menos líneas, más sencillo y fácil de comprender será el código para tus semejantes.
  • Las validaciones de formularios se hacen en el servidor. Por mucho JavaScript que haya puedo usar Tamper Data para enviar directamente datos no válidos.

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.


PHP avanza

24 de mayo de 2009 | 4 comentarios
PHP 5.3 sale del cascarón

Hay novedades en el mundillo de nuestro lenguaje de programación web favorito ;-) En primer lugar, hace unas semanas se lanzó la segunda release candidate de la versión 5.3. Es muy probable que la tercera llegue en pocos días, y espero ansioso la versión final para el verano. Algunas de las novedades que se incluyen en esta versión han sido traídas de la 6 beta, ya que muchos no podíamos esperar ;-). Están destinadas principalmente a mejorar la orientación a objetos y la elegancia del lenguaje, y molan. A destacar:

  • Namespaces, para separar conjuntos de clases y poder repetir nombres. Equivalen a los pakages de Java. Eso sí, personalmente, el carácter \ elegido para separar los espacios de nombres me parece feísimo (manías que uno tiene...). Ejemplo:
    \Foo\Bar\clase::metodoEstatico()
  • Funciones anónimas: una característica que echaba de menos y que se utiliza bastante en JavaScript. Viene a sustituir la chapuza que tenían en create_function(). Ejemplo:
    
    var $sumar = function($a, $b) {
      return $a + $b;
    }
  • Type Hint para escalares: la restricción de tipos en los parámetros de las funciones estaba ya disponible para arrays y objetos desde la versión 5.1. Ahora, con PHPTypeSafe es posible restringir tipos a escalares. Esta característica es quizá la que más me gusta. Ejemplo:
    function repetir(string $cadena, int $veces) {}
  • Un driver nativo para MySQL, que sustituirá al viejo mysql y al mysqli. Se supone que será más "moderno" y rápido.

Pateando a Ruby on Rails

La otra novedad que quería comentar es la existencia de un proyecto para implementar el patrón ActiveRecord —que tan famoso ha hecho a on Rails— en PHP 5.3, precisamente. Está gestándose aún, pero promete más facilidad y rapidez de uso que los ORM que se estilan hoy en día en PHP, Doctrine (del que hablaré en breve aquí) y Propl. Ya se puede descargar la beta de su web. Espero tener tiempo para jugar un poco con él, porque promete bastante.


Error garrafal en Tuenti

20 de abril de 2009 | 2 comentarios

Si hace unas semanas ponía un pantallazo de un buscador de torrent como muestra de lo que no se debe hacer en producción, ahora una web conocida por todos, Tuenti, comete un garrafal error con uno de sus servidores de pruebas (accesible públicamente):

Error garrafal en Tuenti

Con cosas como ésta y el hecho de que tres años después todas las imágenes subidas por los usuarios sigan siendo públicas (puedes probar tú mismo a acceder a la URL de una foto con la sesión cerrada) demuestran la incompetencia absoluta y el verdadero interés de quienes están detrás de Tuenti, hacer negocio. Aunque de eso ya hablaremos con calma en breve...


Relacionando enlaces en Delicious y contenidos en el blog

4 de abril de 2009 | 0 comentarios
Enlazando enlaces en Delicious y contenidos en el blog

Hoy vamos a ver una aplicación experimental de eso que llaman "linked data", una tendencia que podría considerarse web semántica, aunque sin ontologías ni estándares, sino basada en RSS, servicios web y API específicas para cada servicio: se trata de obtener enlaces en Delicious relacionados con los contenidos del blog.

Lo que hace este sencillo algoritmo es comparar las "nubes" de tags: obtiene los últimos enlaces de una cuenta de Delicious, comprueba que contengan las tags más recurrentes en el blog, y, cuanto más concurrentes sean las tags en el blog, más puntuación de afinidad se le da al enlace.

El ejemplo en acción lo puedes ver ahí a la derecha. Las tags más recurrentes del blog (web semántica, web social, evolución de internet...) están presentes en los enlaces.

El algoritmo puede ser útil para saber cómo evolucionan los intereses de redacción y navegación del blogger o aportar contenido añadido al blog. Estoy preparando más artículos con más criterios para evaluar la "cercanía temática" de los artículos, basándose en las etiquetas.

El código de abajo funciona sobre el gestor de contenidos de este sitio. Si a alguien le interesa hacer un plug-in para WordPress que soporte esta funcionalidad lo podemos intentar. Habría que adaptar la obtención de los datos (adaptando las sentencias SQL a la BD de WordPress o sustituyéndolas por llamadas a la API) y el modo de mostrarlos (sin Smarty).

Función para ordenar los enlaces

//Ordena una matriz por el valor de una de sus claves (primer nivel)
//TODO parece que no funciona bien
function array_sort_clave(&$data, $campo) {
  //Crea una función personalizada para el campo
  $codigo_funcion = "return strcmp(\$a['$campo'], \$b['$campo']);";
  usort($data, create_function('$a,$b', $codigo_funcion));
  return $data;
}

Clase Enlace (la utilizo para los enlaces de Delicious y para el blogroll)

class Enlace {
  public $titulo;
  public $url;
  public $icono;
  public $rel;
  public $tags = array();
  
  public function __construct($titulo, $url, $icono, $rel=null, $tags=null) {
    $this->titulo = $titulo;
    $this->url = $url;
    $this->icono = $icono;
    $this->rel = $rel;
    $this->tags = $tags;
  }
}

La "chicha"

//Obtiene una nube de tags correspondiente a todos los post
static public function getTagcloudSimple() {
  $tagcloud = array();
  $cTags = BDConsulta("SELECT tag FROM tags WHERE post IN (SELECT id FROM posts ORDER BY creado DESC)");
  while ($tag = recorrer_resultados($cTags)) {
    $tagcloud[$tag['tag']]++;
  }
  return $tagcloud;
}

//Obtiene enlaces de Delicious, en orden de afinidad con el blog
//La afinidad con el blog se calcula a partir de las tags de los posts del blog. A cada enlace se le asigna un índice de afinidad
//El índice de afinidad se calcula sumando las veces que cada tag del enlace aparece en el blog
static public function getDelicious() {
  $enlaces = self::getEnlacesDelicious();
  $enlaces_con_puntos = array();
  $tags_blog = self::getTagcloudSimple();
  foreach ($enlaces as $enlace) {
    $puntos = 0;
    foreach ($enlace->tags as $tag_del_enlace) {
      $puntos += $tags_blog[$tag_del_enlace];
    }
    $enlaces_con_puntos[] = array('datos'=>$enlace, 'puntos'=>$puntos);
  }
  array_sort_clave($enlaces_con_puntos, "puntos");
  $enlaces_final = array();
  $cnt = count($enlaces_con_puntos);
  //Ordena inversamente la matriz, aunque ordena sólamente las claves
  for ($i=0; $i<$cnt; $i++) {
    $enlaces_final[$cnt-1-$i] = $enlaces_con_puntos[$i];
  }
  //Ordena el array según las nuevas claves. Vaya lío...
  ksort($enlaces_final);
  return $enlaces_final;
}
  
//Se procesa el RSS de los enlaces y se crea un array de objetos Enlace con él
static public function getEnlacesDelicious($limite=30) {
  if (!is_int($limite)) trigger_error("El parámetro debe ser un entero", E_USER_ERROR);
  //$xml = simplexml_load_file(RUTA_L . "delicious.xml");
  $xml = simplexml_load_file("http://feeds.delicious.com/v2/rss/isra00?count=$limite");
  $enlaces = array();
  foreach ($xml->channel->item as $item) {
    $tags_item = array();
    //No vale pasar $item->category directamente al constructor de Enlace, mejor pasarlo a array de cadenas
    foreach ($item->category as $s) {
      $tags_item[] = utf8_decode((string) $s);
    }
    $enlaces[] = new Enlace(utf8_decode($item->title), utf8_decode($item->link), null, null, $tags_item);
  }
  
  return $enlaces;
}

Mostramos los enlaces en una plantilla Smarty

<h3>Enlaces Delicious</h3> <div id="blogroll" class="sb_seccion pq">
    <p>Mostrando enlaces relacionados con el blog. <a href="http://delicious.com/isra00/">Ver todos los enlaces</a>.</p>
    <ul>
    {foreach from=$delicious item=enlace name=i}{if $smarty.foreach.i.index < 10}
        <li>
            <a href="{$enlace.datos->url}">{$enlace.datos->titulo}</a>
            {foreach from=$enlace.datos->tags item=tag}
            <a class="insignificante" href="http://delicious.com/isra00/{$tag}">[{$tag}]</a>
            {/foreach}
            </li>
    {/if}{/foreach}
    </ul>
</div>

Plantillas PHP: cuestión de rendimiento

2 de abril de 2009 | 5 comentarios

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 en 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 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 en PHP, embebiendo los códigos en los documentos HTML, pero sin las chapuzas de siempre, separando las capas de lógica y vista.

Podemos crear una pequeña biblioteca de funciones para gestionar plantillas de este tipo y que los nombre de variables no causen conflictos. Voy a hacer algún proyecto con este sistema, a modo de prueba (es lo que tiene ser freelance, puedo usar la tecnología que me dé la gana ;-). Utilizar plantillas en PHP 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") ?>

pie.tpl.php (otra plantilla de ejemplo, para ser incluida)

<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");

?>

FAIL

1 de abril de 2009 | 0 comentarios

Error en PHP

Buscando un torrent para descargar la última de James Bond he encontrado el pedazo de error que estás viendo. El sitio web, basado en PHP, no se puede conectar al servidor de base de datos por cualquier motivo, y los errores se suceden. Pero el gran fallo no está en que no funcione la página, sino en que se muestren abiertamente los datos de conexión a la base de datos (excepto la contraseña). Uno de los errores más comunes (no es la primera vez que veo cagadas así) y más graves en seguridad es dar detalles sobre el funcionamiento del sistema, en este caso los de la BD. Está claro que el webmaster olvidó desactivar el reporte de errores... porque ¡en producción se debe desactivar el reporte de errores!

Es muy sencillo hacerlo: o bien editar el fichero de configuración de PHP, el famoso php.ini poniendo a 0 la directiva error_reporting o bien incluyendo la siguiente llamada en nuestros scripts:

error_reporting(0);

Esto no quita que haya que "perder de vista" los errores. PHP almacena todos los errores en el archivo de log especificado en la directiva de configuración error_log. Y te recuerdo que esa directiva, si no tienes acceso al php.ini de tu servidor, la puedes cambiar en directo a través de la función ini_set().

Más información sobre directivas de configuración en el manual oficial de PHP.


Arquitectura para una API web en 5 minutos

27 de marzo de 2009 | 1 comentario

Transcribo parte de un hilo en mi foro favorito:

Hola, tengo un sitio en el cual queiro implementar una API para permitir a los sitios que tengan su clave insertar registros en mi base de datos controladamente... algun dato para arrancar?

Tienes que pensar que para cualquier API hay peticiones y respuestas. Las peticiones tienen argumentos y las respuestas, contenido, independientemente de lo que se haga en el servidor, lectura, escritura o baile ;-) Es decir, a nivel de API no hay peticiones de lectura o escritura, eso dependen de la funcionalidad que se implemente. Todas las peticiones HTTP son "iguales" (con muchas comillas).

Ahora bien, para esas cosas hay muchas opciones. Si quieres algo sencillo a la vez que bastante compatible entre plataformas te recomiendo HTTP-GET para las peticiones y HTTP-JSON para las respuestas. Por ejemplo:

http://servidor.com/api/editarCliente.php?clave=xxxx&id=23&nombre=Pepito

Esto sería una petición que, en principio no tendría respuesta con contenido. Es decir, una vez enviada esa petición, se procesa y no hay nada que decir al cliente. O sí... un mensaje de "ok" o "error", aunque sólo sea eso..

Para una petición típicamente de lectura podríamos hacer algo como

http://servidor.com/api/clientesNuevos.php?clave=xxxx&limite=20

Esta petición requeriría los 20 últimos clientes añadidos. El PHP puede ejecutar la consulta, recorrer los resultados y guardarlos en un array. En cuanto tengas el array de resultados, lo único que hay que hacer es:

//Disponible a partir de PHP 5.2
echo $json_encode($my_array);

JSON es un formato de representación de datos compatible con JavaScript y muy usado últimamente. Con estas herramientas podrás implementar APIs de manera rápida y sencilla, a la vez que compatibles con una amplia gama de lenguajes y plataformas. Puedes usarlo también desde tu propia aplicación si deseas una interfaz AJAX, puesto que en JavaScript no necesitas descodificar JSON, ya que es un formato nativo de JavScript, como ya he comentado.


PHPTAL, un digno competidor de Smarty

21 de marzo de 2009 | 3 comentarios

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

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 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 ;-)


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