Delirios de un Informático

PHP, CLI, STDOUT & STDERR

Suelo utilizar PHP como lenguaje para programar ciertos scripts que me faciliten muchas tareas, porque es el lenguaje que más conozco y por lo tanto el más rápido para desarrollarlos. Estos scripts pueden ejecutarse en línea de comandos como cualquier archivo Bash o Python y muchas veces requieren el uso de comandos del sistema que pueden llamarse a través de las funciones exec() o shell_exec(). El problema es que a veces estos comandos muestran una serie de datos que interesa capturar para procesarlos desde PHP y que no logran capturarse mediante estas funciones. Por eso, he rebuscado y encontrado el modo de solucionarlo:

$cmd = "/usr/bin/comando --parametros";
exec("$cmd 2>&1", $out, $err);

De este modo toda la salida de datos se pasa a PHP sin mostrarlo por pantalla :).

Cómo convertir una base de datos MySQL a SQLite

Hace tiempo hablé de SQLFairy, una serie de scripts que permiten convertir bases de datos entre sí, realizando las modificaciones oportunas a los dumps. De lo que no hablé es de cómo convertir una base de datos MySQL a SQLite sin dolor, ya que muchos scripts que circulan por la red no funcionan o están obsoletos. El proceso es sencillo:

sqlt -f DBI --dsn dbi:mysql:mydb --db-user root --db-pass ******** -t SQLite > schema.sql
mysqldump -u root -p --compatible=ansi --skip-opt mydb | grep "INSERT" > data.sql
cat schema.sql | sqlite3 mydb.db && cat data.sql | sqlite3 mydb.db

Lo único a tener en cuenta es revisar el volcado de datos en busca de comillas simples () escapadas, ya que SQLite parece no aceptarlas bien. Basta sustituirlas por alguna cadena de caracteres aleatoria que luego se re-sustituye con una consulta UPDATE.

Este script automatiza el proceso:

#!/bin/bash
sqlt -f DBI --dsn dbi:mysql:$1 --db-user $2 --db-pass $3 -t SQLite > schema.sql
mysqldump -u$2 -p$3 --compatible=ansi --skip-opt $1 | grep "INSERT" > data.sql
iconv -f ISO-8859-1 -t UTF-8 data.sql > data-iso.sql
perl -pe "s/\\\'/''/g" data-iso.sql > data.sql
cat schema.sql | sqlite3 "$1.db" && cat data.sql | sqlite3 "$1.db"
rm -f schema.sql data.sql data-iso.sql

Debe ejecutarse del siguiente modo: ./mysql2sqlite.sh database user password.

elRTE: WYSIWYG basado en jQuery UI

elRTE es un nuevo editor WYSIWYG para la web basado en jQuery UI. Es muy ligero principalmente porque no tener que cargar su propio framework (como hace el pesadísimo TinyMCE) y consta de un único archivo JS, un par de CSS y menos de 100kb de imágenes. Es 100% Open Source y muy fácil de instalar.

Cómo añadir nuevas reglas de validación en Code Igniter

Code Igniter trae de serie una sencilla pero potente librería para la validación de formularios, con una serie de reglas de validación predefinidas. Es posible añadir nuevas reglas externas a la librería creando nuevos métodos en el controlador, pero si esas reglas han de reutilizarse en más de un controlador el principio DRY no se cumple.

La solución pasa por extender la librería nativa añadiendo nuevos métodos que equilvaldrán a nuevas reglas. Por ejemplo, para poder realizar una comprobación con la nueva regla valid_url se extendería la clase nativa del siguiente modo:

<?php

class MY_Form_validation extends CI_Form_validation
    {
    function valid_url($url)
        {
        return (bool) preg_match('/^http:\/\/', $url);
        }
    }

Después, la comprobación se realizaría así:

$this->form_validation->set_rules('url', 'lang:url_incorrecta', 'valid_url');

En caso de ser necesario, puede utilizarse $ci =& get_instance(); para obtener una instancia del controlador y poder utilizar así otras librerías o modelos que puedan ser necesarios. Por supuesto, este truco también sirve para sustituir las reglas de validación nativas 🙂

Actualización: esto puede resultar particularmente útil para crear una regla que permita comprobar la existencia de un elemento en la base de datos cuando un campo que el usuario debe introducir es UNIQUE.

Los navegadores y los búferes de salida

Hace unos días me vi en la necesidad de implementar en una web un proceso que puede tardar uno o dos minutos dependiendo de varios factores. El problema reside en que es un proceso que inicia el usuario y el usuario normalmente es impaciente, por lo que es normal encontrarse con que se pulsa F5 (o un montón de teclas al azar) o se cierra el navegador pensando que la aplicación se ha colgado. Por eso pensé en implementar una barra de progreso con JavaScript (utilizando la estupenda librería jQuery UI) en la que el progreso fuera real y fiable. El proceso lento iría mostrando poco a poco el progreso y mediante JavaScript se interpreta y modifica la barra.

Para obtener progresivamente el contenido de una petición realizada con JavaScript hay varias técnicas y tecnologías (HTTP Streaming, Server-Sent Events…) pero opté por algo mucho más sencillo: cargar el script lento en un <iframe> que en teoría debería funcionar para todos los navegadores. Basta hacer que el proceso vaya mostrando poco a poco bloques de información procesable por JavaScript (JSON, por ejemplo) y con un setTimeout() se comprueba el contenido del <iframe>, obteniendo el último bloque. En mi caso, cada bloque contiene el porcentaje y un mensaje para que se vaya mostrando en la barra de progreso. El código PHP podría ser:

echo json_encode(array('percent'=>10,'text'=>'Iniciando el proceso...')); flush();
echo json_encode(array('percent'=>50,'text'=>'Proceso a la mitad...')); flush();
echo json_encode(array('percent'=>100,'text'=>'Proceso finalizado...')); flush();

El problema es sólo Mozilla mostraba el contenido tras cada flush() mientras que los navegadores basados en Webkit así como Opera e Internet Explorer no mostraban nada hasta que terminaba el proceso. Tras mucho curiosear dí con la solución gracias a este comentario en PHP.net que explica que (exceptuando a los navegadores con motor Gecko) los navegadores no mostrarán nada hasta recibir una etiqueta HTML. Por eso, tras cada echo es necesario añadir una etiqueta como <br> que fuerce al navegador a mostrar el contenido. Además, algunas versiones de Internet Explorer no mostrarán nada hasta haber recibido al menos 256 bytes y Firefox no mostrará las líneas con menos de 8 bytes. Así que con las correcciones, el código PHP anterior quedaría así:

echo str_repeat(' ', 256) . '<br>'; flush();
echo json_encode(array('percent'=>10,'text'=>'Iniciando el proceso...')) . '<br>'; flush();
echo json_encode(array('percent'=>50,'text'=>'Proceso a la mitad...')) . '<br>'; flush();
echo json_encode(array('percent'=>100,'text'=>'Proceso finalizado...')) . '<br>'; flush();

Es importante tener en cuenta que Internet Explorer procesa las etiquetas en mayúsculas, por lo que al acceder mediante JavaScript al contenido del marco oculto habrá que tenerlo en cuenta. En mi caso, al utilizar <br> como separador he de realizar la separación de los bloques mediante una expresión regular con el modificador /i.

También hay que destacar que hay ciertas cosas que pueden provocar que todo esto no funcione, como los módulos de compresión de los servidores web o ciertos módulos de seguridad como mod_security. En estos casos no se enviará nada al navegador hasta haberse completado la petición, por lo que no será posible obtener el contenido del marco oculto hasta que el servidor envíe todo de golpe.