Blog de Israel Viana

Como las fechas de los últimos posts indican, he abandonado el blog. Lo mantengo on-line por si alguien encuentra útil algún artículo, y quizá algún día tenga tiempo y ganas para retomarlo. Gracias a todos, ha sido un placer.

Artículos sobre php

Mejor rendimiento de Drupal con GraphicsMagick

14 de mayo de 2010 | 0 comentarios

Un pequeño (y espero útil) apunte antes de que penséis que he abandonado completamente el blog :-(

Se trata de un consejo para mejorar el rendimiento de nuestro sitio Drupal. El sistema utiliza por defecto la biblioteca GD para manipular imágenes (por ejemplo, generar miniaturas). El módulo ImageAPI inserta una capa de abstracción en Drupal que permite elegir qué biblioteca gráfica usar, GD o ImageMagick. Es mejor utilizar ImageMagick, ya que es una biblioteca independiente de PHP y, por ello obtendremos mejor rendimiento total en Drupal, y además es más estable (si falla GD puede caer todo el motor de PHP, sin embargo, si falla ImageMagick no caerá, ya que es un binario diferente).

Pero aún mejor: existe un proyecto llamado GraphicsMagick, que es un fork de ImageMagick y que es más estable y, sobre todo, con mejor rendimiento, ya que consume menos memoria y es más rápida. Y lo que es mejor, tiene la misma API que ImageMagick (el comando convert), con lo cual podemos engañar a ImageAPI diciéndole que use ImageMagick cuando en realidad tenemos instalado GraphicsMagick.

Para lograr esto no tienes más que instalar GraphicsMagick y el módulo ImageAPI de Drupal, en dos sencillos pasos. Supongamos que trabajamos con un servidor Debian/Ubuntu (en otros sistemas operativos no debería ser muy diferente):

  1. Instalamos GraphicsMagick: apt-get install graphicsmagick-imagemagick-compat

    Podemos probar que GraphicsMagick funciona escribiendo convert en la consola:

    isra@salon:~$ convert
    GraphicsMagick 1.3.5 2009-01-26 Q8 http://www.GraphicsMagick.org/
    Copyright (C) 2002-2009 GraphicsMagick Group.
    Additional copyrights and licenses apply to this software.
    See http://www.GraphicsMagick.org/www/Copyright.html for details.

    Usage: convert [options ...] file [ [options ...] file ...] [options ...] file

    [más opciones]
  2. Instalamos el módulo ImageAPI y lo activamos en /admin/build/modules (módulos ImageAPI e ImageAPI ImageMagick).
  3. Accedemos a la configuración de ImageAPI en Administración > Configuración del sitio > ImageAPI (/admin/settings/imageapi) y veremos el siguiente mensaje: "The ImageAPI ImageMagick module is the only enabled image toolkit. Drupal will use it for resizing, cropping and other image manipulations".
  4. Si también hemos activado el módulo ImageAPI GD2, nos dará a elegir (seleccionamos ImageAPI ImageMagick como opción por defecto):

¡Y listo! Fácil y rápido. Podemos comprobar que funciona accediendo a Administración > Configuración del sitio > ImageAPI > Configurar (/admin/settings/imageapi/config), lo que nos mostrará información sobre GraphicsMagick (versión, tipos soportados, etc):

¿He hecho benchmarks para comprobar la ganancia de rendimiento con respecto a ImageMagick?

Pues no, lo siento. Pero los chicos de GraphicsMagick sí los han hecho, y son bastante jugosos ;-)

Pero... ¿no es necesaria la extension imagick de PHP?

No, no es necesaria, ya que ImageAPI interactúa con ImageMagick/GraphicsMagick a través del binario convert.

Iré escribiendo algunos artículos más sobre técnicas fáciles para optimizar el rendimiento de Drupal.


Relanzando Caminayven, otra historia de éxito con WordPress

1 de febrero de 2010 | 3 comentarios
Caminayven

Como comentaba en el artículo "Primeras experiencias con Wordpress... y muchas nueces ;-)", me estoy introduciendo en el mundillo de WordPress. Nueces San Ignacio no ha sido el único proyecto que he realizado con este gestor de contenidos. Hoy quiero contarte mi experiencia en otro proyecto un poco más grande: Caminayven. Se trata de una revista católica on-line, que lleva más de cinco años on-line sobre PHP-Nuke con más de mil artículos, un montón de redactores y, afortunadamente, un número creciente de visitas.

Hace dos años que empecé a trabajar en un nuevo gestor de contenidos para Caminayven. Empecé programando desde cero un CMS bastante ambicioso, y aproveché para hacer mis primeros pinitos con JavaScript-RPC (una especie de alternativa a AJAX para comunicación asíncrona, basada en carga dinámica de scripts e iframes). El resultado fue desastroso, aunque aprendí la valiosa lección de que no es bueno reinventar la rueda.

Pero —cabezón de mí— volví a la carga a principios del año pasado ayudado por CakePHP, un framework que me facilitó bastante las cosas, pero que seguía sin ser suficiente. Por falta de tiempo e ilusión el proyecto volvió a quedar abandonado, hasta que hace unos pocos meses empecé a preguntarme si WordPress cubriría las necesidades básicas de Caminayven: publicación de artículos, galerías de fotos, acceso sencillo y elegante a la información (archivos, destacados, relacionados...) y poco más.

De nuevo, un análisis realista y concreto de la Arquitectura de la Información para el proyecto puso las cosas en su lugar. ¿Para qué programar una gestión de usuarios, workflow, editor WYSIWYG, feed RSS, categorías y tags... si Wordpress ya lo hace? Dicho y hecho: convertir las antiguas plantillas de CakePHP en un theme para WordPress fue cuestión de dos tardes (copiando y modificando Kubrick).

Aunque he tenido algunos problemillas en el theming, quizá causados por mi ignorancia sobre WordPress o por su estructura interna —en mi opinión poco elegante—: el formato de las fechas y la internacionalización. El formato de las fechas, aunque se especifica en el dashboard no se respetaba en el tema Kubrick, además de que los nombres de los meses no se traducen. Con respecto a la internacionalización (más concretamente la traducción), utilizo la versión en castellano de WordPress, pero aun así muchos mensajes están sin traducir, por lo que he tenido que traducir manualmente algunos fragmentos del theme.

En lo que respecta a la funcionalidad, he utilizado el plug-in Author Image y un código propio en el theme para mostrar, a la derecha de cada artículo, una breve ficha de su autor.

Galería

Caminayven también destaca por sus reportajes gráficos, la mayoría de ellos realizados por nuestro querido Javier Cebreros. Para publicarlos probé en primera instancia Awsom PixGallery. Aunque era sencillo utilizarlo y bonito ver una página para cada álbum/foto, el rendimiento es horrible (regenera continuamente las miniaturas), el código es patético (un único y monolítico script de miles de líneas, mezclando PHP y HTML) y el manejo de URL fallaba como una escopeta de feria. Ahora utilizamos NextGEN Gallery, que es justamente lo contrario: un buen rendimiento, mucha flexibilidad a la hora de subir fotos (subida múltiple por HTTP, FTP, etc) y una forma de mostrar los álbumes muy chula. Una posible mejora a este plug-in sería la construcción de colecciones a partir de álbumes en Flickr o Facebook (en Caminayven alojamos algunas fotos en estas redes sociales), y lo añado a mi lista de cosas-que-haré-algún-día.

Otros plug-ins

También he añadido WP-Cache y All-in-One-SEO-Pack para optimizaciones de rendimiento y SEO, respectivamente. Ambos funcionando muy bien y sin problemas. WP-Cache y los consejos de YSlow me han ayudado a hacer Caminayven más rápida, y aún se puede optimizar mucho más.

Migración

Hasta aquí la parte bonita. Pero no hay que olvidar que Caminayven es un proyecto de cierta envergadura y con una trayectoria de cinco años sobre PHP-Nuke, un gestor de contenidos que odio por ser el perfecto compendio de malas prácticas (de hecho, hace dos años nos colaron una inyección SQL que permitió a un niñato destrozarnos la página...). Así que la migración no se plantea fácil. Por una parte, los artículos se escribieron desde el editor WYSIWYG de Internet Explorer y desde Word, así que te puedes hacer una idea de la impeorable calidad del marcado HTML. No se puede copiar y pegar el contenido de una tabla de PHP-Nuke en otra de WordPress, así que la solución aparente está filtrando el HTML con unas decenas de expresiones regulares caseras y las funciones de inserción del API de WordPress. Además, las categorías han cambiado, y he tenido que hacer una tabla de "traducción" de las viejas a las nuevas categorías.

Y, por si fuera poco, nuestros artículos salen en Google News y perder las antiguas URL (modules.php...) sería desastroso para nuestro posicionamiento, tanto en Google News como en los buscadores en general. Así que ha habido que hacer redirecciones 301 e intentar encontrar el artículo por su antiguo ID... también he tenido que añadir el ID de los artículos a las URL, por exigencia de Google News (un poco caprichoso, sí).

Analítica y redes sociales

Al igual que con Nueces San Ignacio, mi trabajo en este proyecto ha ido más allá de lo puramente ingenieril, ya que he diseñado, redactado y otras actividades. Las dos más importantes para el éxito del proyecto han sido la analítica web y la promoción en redes sociales. En cuanto a lo primero, Google Analytics es una gran herramienta pero se está quedando corta para alguna información que necesito saber (los referer exactos cuando un usuario encuentra un error 404 o cosas así), así que estoy introduciendo pequeños códigos de seguimiento propios en la parte del servidor.

Con respecto a las redes sociales, hemos redefinido nuestra estrategia en Facebook, pasando de grupo a usuario corriente. El motivo primario es la visibilidad, ya que todas las novedades que publiquemos con el usuario Caminayven aparecerán en el timeline de nuestros amigos. En cuanto al contenido, enlazamos los artículos más destacables en el status, publicamos todos los posts con NetworkedBlogs y subimos los reportajes gráficos. La estrategia está funcionando en parte, ya que las visitas desde esta fuente han aumentado, pero nuestros seguidores (alrededor de 200) no son muy activos corriendo la voz... es decir, que la regla del 90-90-1 no se cumple del todo.

También nos hemos dado de alta en Twitter (@caminayven), aunque todavía no hay mucha actividad. Esperemos que en los próximos meses la cosa vaya aumentando.

Conclusiones

  • WordPress mola. No es la panacea, pero mola.
  • Hacer una revista católica en internet no es fácil, y crear una comunidad alrededor de la misma mucho menos, pero estamos en el camino.
  • Caminayven es un proyecto apasionante al que siempre se le puede sacar punta, quedan muchas cosas por hacer, pero de momento he cumplido con mi deber ;-)
  • La audiencia está respondiendo: las visitas, en general, han aumentado, y en particular, el promedio de páginas por visita. Pero, sobre todo, los comentarios, ese elemento mágico y maravilloso que da vida a un sitio web :-)

En fin, otro proyecto cerrado por el momento... dentro de un mes te cuento el siguiente... ¡más ambicioso y emocionante todavía!.


Primeras experiencias con Wordpress... y muchas nueces ;-)

26 de diciembre de 2009 | 2 comentarios

Web de Nueces San Ignacio Todo desarrollador web debe saber trabajar con uno o varios sistemas de gestión de contenidos (CMS). Estos programas nos ahorran reinventar la rueda cada vez que tenemos que publicar contenidos en la web, gestionar usuarios y la administración del sitio, organizar nuestro código de acuerdo a un estándar, etc, etc, etc.

Personalmente siempre he sido aficionado a los CMS, pero a nivel de usuario. Me encanta conocer y probar los gestores de contenido, aunque hasta hace poco no había desarrollado proyectos —proyectos de verdad— con una de estas herramientas, por vagancia para afrontar la curva de aprendizaje y por afán de reinventar la rueda (puede ser divertido y muy pedagógico, pero no es práctico si queremos buenos resultados).

El caso es que hace un par de meses comencé un proyecto web para un amigo que cultiva nueces y me animé a probar Wordpress, animado por la experiencia de los chicos de Atracciona, que utilizan este gestor de blogs para hacer webs corporativas. Mi proyecto era dar presencia web a la Nueces San Ignacio y captar clientes a través de Internet, por lo que a nivel técnico las exigencias eran mínimas (unas cuantas secciones de texto rico, fotos, un formulario de contacto y poco más).

La experiencia con Wordpress ha sido impresionante. A pesar de que no me gustan sus tripas (algunos fragmentos de código son verdadero spaghetti code), diseñar un theme con Wordpress es increíblemente fácil y rápido. Una vez tuve el diseño en HTML/CSS, pasarlo a Wordpress fue cosa de una noche. No toma más esfuerzo que copiar un tema (por ejemplo, Kubrick) e ir modificando las plantillas, adaptando el CSS y cambiando el marcado que sea necesario.

El resultado, una web corporativa con un panel de administración muy potente, la disponibilidad de miles de plug-ins y el respaldo de un software probado en millones de servidores.

Logo de Nueces San IgnacioMás allá de la programación, también diseñé la web, el logotipo —y las tarjetas de visita, y un dossier comercial, todo ello con software libre e imágenes libres— y todo lo relativo a márketing en redes sociales, SEO y SEM. Y aunque el mercado en el que se mueve la empresa no depende en gran medida de Internet (el objetivo es vender toneladas de nueces, y el público objetivo hoy en día son distribuidores y grandes superficies), poco a poco la estrategia web va dando fruto. La experiencia ha sido gratificante, porque no es lo mismo hacer un proyecto para un cliente (con la exigencia de calidad y la concentración de sólo programar) que para un amigo (he tenido total libertad para diseñar, desplegar, redactar, etc). Te lo tomas con más calma, le dedicas las horas que puedas y el resultado acaba siendo muy satisfactorio, porque de un modo u otro me he involucrado en la empresa.

Conclusión, Wordpress es un muy buen gestor de blogs que también puede ser usado como gestor de contenidos genérico para sitios web pequeños, con una rapidez en la creación de themes que nunca antes se había visto. No obstante, cuando tenga tiempo he de revisar otros CMS para sitios web corporativos. Hace algún tiempo probé CMS Made Simple, pero no me convenció. Sin embargo, MODx promete bastante.

Cuestiones todas a resolver en 2010. Feliz Navidad y que el año que se avecina sea el mejor de tu vida :-)


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


Maneja las CSS como un profesional

14 de septiembre de 2009 | 13 comentarios

Una parte importante de cualquier aplicación o sitio web son los estilos CSS. Las hojas de estilos se suelen escribir aisladas de la aplicación, sus tecnologías y metodologías. Además, suelen ser servidos como archivos estáticos, de forma que no pasan por la base de datos ni el resto de la aplicación. Existen buenas prácticas relacionadas con la legibilidad y mantenibilidad de las hojas de estilos, pero pocas veces se opta por mezclar las CSS con las aplicaciones, entre otras cosas porque servirlos como ficheros estáticos es más rápido que procesarlos con PHP, y porque a menudo los diseñadores que escriben los estilos no conocen las tecnologías de programación.

No obstante, como se explica en "Supercharge Your CSS with PHP Under the Hood" y demuestran algunas aplicaciones como Plone o phpMyAdmin, mezclar las tecnologías de programación con el lenguaje de estilos CSS puede aportarnos más potencia y comodidad a la hora de escribir, mantener y aumentar el rendimiento de las stylesheets. Y por el mismo precio también te presentaré CssDispatcher, una biblioteca que te ayudará a hacer todo eso casi sin esfuerzo.

Variables y funciones en CSS

En programación, si vas a utilizar varias veces un dato, lo almacenas en una variable para manejarlo más cómodamente. El mismo principio podemos aplicar, por ejemplo, con los colores en una hoja de estilos. De este modo evitamos repetir (es decir, recordar o copiar-pegar) códigos hexadecimales. Por ejemplo, el siguiente estilo:

.paginacion { border: 1px solid red; }
.paginacion a { color: red; }

...quedaría de este modo:

.paginacion { border: 1px solid <?=$rojo ?>; }
.paginacion a { color: <?=$rojo ?>; }

Como ves, he utilizado las etiquetas cortas y el operador de impresión <?= en lugar de echo para abreviar. Establecer variables para colores facilita la tarea de crear esquemas de colores intercambiables (y más legibles):

body { color: <?=$color_texto ?>; }
a { color: <?=$color_enlace ?>; }
h1 { color: <?=$color_primario ?>; }

#divCentral { border: 1px solid <?=$color_primario ?>; }

Como ya he comentado, phpMyAdmin hace uso de esta técnica. Aquí tenemos un pequeño ejemplo:

th {
    font-weight:        bold;
    color:              <? echo $GLOBALS['cfg']['ThColor']; ?>;
    background:         <? echo $GLOBALS['cfg']['ThBackground']; ?>;
}

En este caso no se usa el operador = para imprimir, y sí echo. Es recomendable hacer lo mismo ya que en muchos servidores (sobre todo con las versiones más recientes de PHP) las etiquetas cortas están desactivadas.

Además de colores, puedes utilizar las variables para calcular medidas relativas. Por ejemplo, si tenemos un layout de 800px de ancho con dos columnas, una de 600px y otra de 200, podemos expresarlo con medidas relativas (%) para hacer más accesible nuestras hojas de estilo:

#columna1 { width: <?php echo ($ancho_columna1/800)*100 ?>%; }
#columna2 { width: <?php echo ($ancho_columna2/800)*100 ?>%; }

También podemos utilizar propiedades completas como variables:

h1 { <?php echo $negrita ?>; }

Donde $negrita valdría font-weight: bold.

Llegados a este punto, seguramente estés pensando "muy bonito, pero ¿dónde defino las variables?". En el tutorial de NetTuts los ejemplos muestran las declaraciones de variables en el mismo fichero que las CSS, pero no creo que sea una buena práctica. phpMyAdmin tiene ficheros separados donde se definen los valores, aunque utilizar el array $_GLOBALS no es muy elegante, sobre todo si tu aplicación es orientada a objetos.

Como ya adelanté al comienzo, vamos a ver una biblioteca llamada CssDispatcher que facilita la gestión de CSS con PHP. Esta librería propone un método concreto de definir las variables de los estilos, de forma que se identifiquen fácilmente y además los nombres no causen conflictos con el resto de variables de la aplicación. Este método es sencillamente añadir las variables a una instancia de la clase Css a través del tipado dinámico:

$general = new Css('general.css.php');
$general->fondo = '#eee';
$general->color_borde = 'red';
$general->cabecera = 2.1;

Aunque las variables son introducidas en el objeto, al usarlas en la plantilla CSS sólamente tenemos que especificar su nombre, como variables "tradicionales":

body { background: <?php echo $fondo ?>; }

De este modo podemos asignar variables a cuantas hojas de estilos queramos, sin que interfieran los nombres:

$general = new Css('general.css.php');
$general->altura_cabecera = '5em';

$ie_hacks = new Css('ie_hacks.css.php');
$ie_hacks->altura_cabecera = '5.1em';

Ah, por cierto, las variables también pueden ser de tipo función anónima. Por ejemplo...

$general->layout = function($proporcion) {
	return $proporcion / 800;
}

...que se podría aplicar de este modo:

.columna1 { width: <?=$layout(100) ?>em; }

Un último apunte sobre CSS "plantilladas": te recomiendo que las nombres con extensión .css.php, como se ve en los ejemplos, o bien .css.tpl. Utilizar la extensión simple .css puede llevar a confusiones... pero es sólo un consejo.

Aumentando el rendimiento

Tanto si usamos variables en las CSS como si no, podemos mejorar el rendimiento si las procesamos con PHP, tanto en el servidor (tardamos menos en procesarlas) como en el cliente (le llegan antes).

Un ejemplo es el envío de varias hojas de estilo como un solo fichero. Mientras las buenas prácticas de CSS recomiendan separar las hojas de estilo por secciones o por navegadores (por ejemplo, ie-hacks.css, webkit.css, etc), las buenas prácticas de rendimiento nos aconsejan minimizar el número de peticiones HTTP. Así pues, ¿qué escogemos: mantenibilidad o rendimiento? Pues yo me quedo con las dos: escribamos las CSS en ficheros separados y juntémoslas a la hora de servirlas.

CssDispatcher nos permite hacerlo de una forma muy sencilla. Una vez que hemos creado instancias de la clase Css para cada hoja de estilo, las añadimos a un objeto CssDispatcher:

$general = new Css('ejemplo.css.php');
$general->fondo = '#eee';

$otra = new Css('example2.css.php');
$otra->negrita = 'font-weight: bold';

//Creamos un dispatcher
$estilos = new CssDispatcher;

//Añadimos las CSS al dispatcher
$estilos->add($general);
$estilos->add($otra);

$estilos->render();

Cross-browser

Otro de los problemas comunes cuando se escriben estilos es la compatibilidad entre navegadores. No todos renderizan las CSS de igual forma, así que es habitual escribir hojas de estilo con propiedades específicas para Internet Explorer, Safari/Chrome, etc. Dependiendo del que esté utilizando el usuario se envía una u otra.

Se han venido utilizado tres métodos diferentes para cargar hojas de estilos específicas:

  • Comentarios condicionales (sólo funcionan en IE): para añadir CSS específicas de Internet Explorer basta con envolver la línea de llamada (<link rel="stylesheet"...) con las directivas <!--[if IE 6]> y <![endif]-->.
  • JavaScript: una vez cargada la página, se ejecuta un script que comprueba el navegador y carga dinámicamente la hoja de estilos correspondiente. En consecuencia, los usuarios que no ejecuten JavaScript (por seguridad o por usar un navegador viejo/limitado) no podrán cargar hojas de estilo específicas.
  • En el servidor:: antes de enviar la página, el script que la genera comprueba la cabecera HTTP User-Agent y escribe las invocaciones correspondientes al generar HTML.

Elegiremos el último método por ser más seguro y compatible. Con CssDispatcher definir CSS específicas de navegador es tan fácil como añadir un segundo parámetro en el constructor de la clase Css:

//Enviará la hoja de estilos si el navegador del usuario es Internet Explorer 6
$ie_hacks = new Css('ie_hacks.css.php', Css::UA_IE6);

Puedes especificar las siguientes familias de navegadores: Internet Explorer 6 (Css::UA_IE6), Internet Explorer >6 (Css::UA_IE), Gecko (Css::UA_GECKO), WebKit (Css::UA_WEBKIT). También puedes utilizar los sinónimos UA_MOZILLA, UA_FIREFOX, UA_SAFARI y UA_CHROME respectivamente. Para más información puedes ver la referencia de CssDispatcher.

En resumen

Hemos visto varias técnicas que nos ayudan a manejar de forma más eficaz y eficiente las hojas de estilos. Puedes utilizar las que necesites, tanto si quieres hacerlo de forma manual como utilizando CssDispatcher (u otras bibliotecas, si las encuentras dímelo!). A saber:

  • Asignación de variables en espacios de nombres seguros.
  • Hojas de estilos específicas de un navegador.
  • Minimización del código CSS.
  • Unión de hojas de estilos para un envío único.

Rendimiento en el servidor con cache

No hay que olvidar que generar estilos con CssDispatcher provoca una carga en el servidor por los accesos a disco (para obtener las CSS y otro para obtener los scripts) y por el tiempo de compilación y ejecución. Por ello, una buena técnica para aumentar la velocidad y reducir sobremanera el throughput es cachear el resultado final de la ejecución de CssDispatcher. Para cachear la salida de un script existen varias técnicas:

  • Cache en el servidor web: la salida de una petición se almacena en memoria o disco y se sirve sin ejecutar nada de PHP. En Apache esto es posible con el módulo mod_cache y sus motores de almacenamiento, mod_disk_cache y mod_mem_cache.
  • Cache a través de PHP: esta técnica hace básicamente lo mismo que la anterior, pero en vez de estar gestionada por el servidor web se gestiona con scripts PHP. Por ello, aunque no es necesario compilar y ejecutar los scripts de CssDispatcher sí es necesario ejecutar el script de control de cache, por lo que esta solución no es tan buena como mod_cache. No obstante, en servidores compartidos y otros entornos de escaso control es una buena solución. Además, el script de control de cache se puede cachear con los sistemas de cache de bytecodes como APC o eAccelerator, aunque eso ya es un tema aparte.

MagiSQL ha muerto

7 de agosto de 2009 | 4 comentarios

Hace cerca de un año que comencé un proyecto llamado MagiSQL, con el objetivo de crear una herramienta modular de gestión de bases de datos relacionales, compatible con múltiples sistemas de BBDD y con licencia libre, por supuesto.

Me he planteado muchas veces la viabilidad y utilidad del proyecto, ya que la competencia es muy dura (phpMyAdmin, pgAdmin, Oracle Enterprise Manager, etc etc etc) y exige reinventar la rueda en la mayoría de los módulos. Además, para que el proyecto fuese exitoso no sólo era necesario que me dejase los ojos durante muchas horas, sino que más programadores se sumasen al proyecto y desarrollasen módulos compatibles con los sistemas gestores de bases de datos, ya que yo no tengo tiempo ni medios para hacer MagiSQL compatible con Oracle, MS-SQL Server, PostgreSQL, MySQL y DB2, como está planteado en los objetivos del proyecto (de momento sólo funcionaba con MySQL). Además, este verano he reescrito por completo el núcleo, lo que ha retrasado y complicado aún más el desarrollo.

Por estos motivos y muchos otros (baja productividad, falta de tiempo y concentración, cuestiones personales...) he decidido no continuar el proyecto. Nunca ha alcanzado un nivel de madurez mínimo como para ser usable, y la gran parafernalia de módulos, clases y patrones hace bastante inútil el código que hay en mi disco duro y en el SVN de Sourceforge. En resumen, que se irá todo a la basura.

¿Todo? Eso significaría tiempo perdido, derrota, fracaso. Y aunque no deja de serlo, es un fracaso del que estoy orgulloso y del que he aprendido un montón, entre otras cosas:

  • He ideado el patrón de diseño "Application Status", del que tendréis más noticias dentro de algún tiempo... ;-)
  • Me he acostumbrado a escribir documentación en línea (phpDoc) y en inglés.
  • Me he familiarizado con la biblioteca AdoDB.
  • Me he hartado de Smarty. Era el último proyecto en el que lo usaba, y desde ahora sólo utilizaré plantillas PHP.
  • He aprendido más cosas sobre la reescritura de URL y expresiones regulares.
  • He comprendido e implementado multitud de patrones.
  • Me he familiarizado con las excepciones de PHP y los métodos mágicos.
  • He explotado las novedades de PHP 5.3.
  • He encontrado un buen entorno de programación en PHP sobre Linux (Netbeans, KCacheGrind, etc).
  • Me he convencido una vez más de que reinventar la rueda es friki, divertido pero inútil y bastante frustrante.
  • He escuchado cientos de discos mientras hacía todo eso ;-)

Bueno ¿y ahora, qué? Ya que el proyecto era para una asignatura de la carrera (aprovecho para decir que, tras pasar al Grado en Informática en la UCAM, he tenido que volver a matricularme de una asignatura que ya tenía aprobada), será sustituido por un motor de almacenamiento en MySQL para Object Freezer. Object Freezer es una biblioteca escrita por Sebastian Bergmann para almacenar objetos en la base de datos CouchDB sin preocuparse de SQL, DAO y demás inventos del pasado ;-)

PD: perdón por saltarme la canción del lunes, no tengo mucho tiempo últimamente para postear, pero os he dejado un regalito en twitter ;-)


La licencia de PHP prohíbe nombrar a tus proyectos "PHPnosequé"

17 de julio de 2009 | 0 comentarios

Navegando por la documentación de PHP, he ido a parar a la página de licencia. Es esta página, además del texto legal se dan algunas explicaciones e indicaciones, entre las cuales está la instrucción de no usar la denominación "PHP" en nombres de proyectos que utilicen esta tecnología.

De hecho, esta indicación viene apuntada en una de las cláusulas la propia licencia:

4. Products derived from this software may not be called "PHP", nor may "PHP" appear in their name, without prior written permission from group@php.net. You may indicate that your software works in conjunction with PHP by saying "Foo for PHP" instead of calling it "PHP Foo" or "phpfoo"

Más claro agua. Así que proyectos como phpMyAdmin, CakePHP, PHP-Nuke y un interminable etcétera están inclumpliendo los términos de la licencia, a nos ser que cuenten con un permiso especial del grupo PHP (ignoro si es así).

Por supuesto, esto no significa que Rasmus Lerdorf y compañía tengan "patentada" la denominación PHP, sino que no puedes usarla en proyectos que se basen en su tecnología, ya que se consideran derivados de PHP. Una curiosa cláusula que sorprende que no hayan leído o no hayan atendido los responsables de tantos proyectos exitosos.


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.


Genera tus Entidad/Relación circulares con SchemaBall-PHP

8 de junio de 2009 | 0 comentarios

Gráfico generado con SchemaBall-PHP

Inspirándome en el SchemaBall de Martin Krzywinski este fin de semana he escrito SchemaBall en PHP. Se trata de un generador de gráficos que representan las tablas de una base de datos y sus relaciones, al estilo de un diagrama entidad/relación. Aún está bastante verde, y la mayor carencia que tiene es que no distingue relaciones de cardinalidad N:N, es decir, aquellas donde la relación está expresada en una tabla intermedia y dos claves foráneas. Además, sólo es compatible con MySQL, aunque mi intención es darle compatibilidad con más gestores de bases de datos, e integrarlo en MagiSQL, del que tendrás noticias en breve ;-)

Por lo demás, añadiré al gráfico alguna representación de los campos, para poder visualizar la complejidad de las tablas de algún modo. Además, me gustaría añadirle interactividad, pero para ello habría que cambiar completamente la plataforma gráfica, pasando de imágenes PNG generadas con GD al Canvas de HTML o incluso Flash.

En fin, sólo es un adelanto para una librería que espero que crezca y se convierta en una herramienta útil para los diseñadores/administradores de bases de datos. ¿Opiniones? ¿Sugerencias? ¿Críticas? ¿Insultos racistas? Los comentarios están abiertos y en cuanto el SVN me funcione (hoy los astros se han alineado favorablemente xD) subiré el código.

PD: la librería para generar este tipo de gráficas estará disponible de forma independiente, para que se pueda utilizar para información diferente al esquema de una base de datos.


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.


El curioso método getDocComment()

29 de mayo de 2009 | 0 comentarios

Uno de los métodos de la clase ReflectionFunction del API de reflexión de PHP se llama getDocComment() y devuelve los comentarios de documentación de una función o método. Por ejemplo:

/**
 * @param   int  $a Primer sumando
 * @param   int  $b Segundo sumando
 * @return  int  Suma de $a + $b
 */
function suma($a, $b) {
    //Otro comentario
    return $a + $b;
}
	
$funcion = new ReflectionFunction("suma");
echo $funcion->getDocComment() . "\n";

El código anterior devolverá el comentario completo:

isra@isra:~$ php -f reflexion.php
/**
 * @param   int  $a Primer sumando
 * @param   int  $b Segundo sumando
 * @return  int  Suma de $a + $b
 */

Hay que recalcar que no devuelve todos los comentarios, sino sólo los de documentación. Por eso si olvidamos poner dos asteriscos al inicio, no devolverá nada:

isra@isra:~$ php -f reflexion.php

Tampoco devolverá nada si utilizamos el símbolo de comentario de una línea //:

// @param   int  $a Primer sumando
// @param   int  $b Segundo sumando
// @return  int  Suma de $a + $b
function suma($a, $b) {
    //Otro comentario
    return $a + $b;
}
	
$funcion = new ReflectionFunction("suma");
echo $funcion->getDocComment() . "\n";
isra@isra:~$ php -f reflexion.php

En fin, un método interesante que puede servir para simplificar y aumentar el rendimiento de los sistemas de documentación como phpDocumentator, y también puede ser útil en el control de errores, combinándolo con demás métodos de ReflectionFunction.

Por cierto, hablando de reflexión, aprovecho para recomendarte una interesantísima clase de Thiago Toledo para serializar objetos en XML, gracias a la reflexión de PHP5. Para que luego digan que PHP tiene una orientación a objetos de juguete.


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.


Drupal 7 será el CMS semántico

17 de mayo de 2009 | 1 comentario

Sólo una pequeña nota para informar de lo que he leído en Sitepoint sobre la nueva versión de Drupal. Si hasta ahora era de los mejores CMS (sus premios le avalan), compitiendo directamente con el "monstruo" Plone, con este movimiento, y si todo sale bien creo que se convertirá en el súper-CMS del presente y el futuro (perdona que me ponga en plan gurú-profeta amarillista).

La idea es darle a Drupal la forma necesaria para convertirse en un nodo de la web semántica, perfectamente interconectado por Web Services, REST o HTTP sin más, que sea capaz de tomar y servir información de otros nodos, permitiendo a los usuarios localizar información dentro y fuera del sitio web. Lo que no sé todavía es si el almacenamiento se mantendrá como hasta ahora, en una base de datos relacional, o por el contrario pasarán a usar una base de datos semántica. Si se diera el primer caso (y así parece, según los avances de Carlos Rincón), deberían utilizar una especie de ORM para mapear las descripciones en la BD, y seguramente esa capa fuese reutilizable más allá de Drupal (como pasó con su motor de plantillas).

Linked data en Drupal 7 (datos enlazados)

Drupal pasa dejando huella, haciendo avanzar el ecosistema de programación PHP y la web en general. Gracias y felicidades, Drupal.

PD: la imagen está alojada por Facebook Inc., hosting gratuito de imágenes. Gracias a su falta de privacidad me voy a ahorrar unos eurillos...


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.


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.


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