Categoría: PHP

Autentificar usuarios en PHP

passwordSe ha escrito mucho sobre este tema, y es fácil verificarlo con una sencilla búsqueda en Google, pero no me resisto a añadir mi propia versión.

A veces necesitamos crear sitios web en los que una parte o todo el contenido debe ser accesible sólo por determinados usuarios. También es posible que queramos personalizar ciertas partes del contenido, dependiendo de quién sea el usuario.

En cualquiera de estos casos, será necesario :

  1. Proporcionar una forma de acceso y verificación de usuarios.
  2. Diseñar una forma de evitar el acceso a determinadas áreas y funcionalidades del sitio.

Autentificar usuarios

La primera parte consiste en verificar el acceso a usuarios. Para ello primero obtendremos ciertos valores que nos permitan identificar a un usuario de forma clara, y después verificaremos que efectivamente, para esos datos existe un usuario con acceso.

Los datos a obtener puede que no sean tan evidentes como parece. Por supuesto, como todos estamos acostumbrados a usar formularios de identificación (o login), damos por hecho que se necesitan al menos dos valores para autentificar la identidad de un usuario. En la mayor parte de las aplicaciones esto es cierto. Un dato identifica al usuario, y el segundo lo autentifica.

Si sólo leemos un identificador, cualquier persona que conozca el identificador de otro usuario podría entrar al sistema. El identificador suele ser conocido, o al menos no siempre es un secreto. Normalmente se usa un nombre, un apodo (un nick), o una dirección de correo. Con sólo el identificador podríamos determinar quién se ha conectado, pero no podríamos estar seguros de que el usuario es quien dice ser.

Si sólo leemos el dato de autentificación, es decir, una contraseña, probablemente estaremos seguros de quien se ha conectado; pero, a no ser que sea el administrador quien asigne las contraseñas, se trata de una técnica peligrosa, ya que es imposible asegurar que dos usuarios no usen la misma contraseña por casualidad.

llavesPodemos pensar en las contraseñas como si fuesen llaves. Las llaves son tanto más seguras cuanto más difíciles sean de copiar. Sin embargo, el acceso a una página no es como el acceso a un edificio, se parece más a una caja de seguridad de un banco. Lo que protegemos en una página es la propiedad de cada usuario. Generalmente cada usuario que accede a una página protegida sólo tiene acceso a sus datos, y sólo algunos usuarios especiales (los administradores), tienen acceso a datos de otros usuarios. Los datos de cada uno estarán seguros siempre que las llaves no se dupliquen o se presten, o en el caso de páginas web, siempre que no se compartan.

Usando los dos valores, usuario y contraseña, la aplicación sólo tiene que asegurarse de que los identificadores de usuario son únicos. En nuestras aplicaciones diseñaremos mecanismos para impedir que dos usuarios usen el mismo identificador. Aunque nada impide, en principio, que varios usuarios usen la misma clave.

Es muy frecuente usar una dirección de correo como identificador de usuario. Esto tiene algunas ventajas, por ejemplo:

  • Permite una autentificación extra. La técnica habitual es enviar un mensaje a la dirección proporcionada, que el usuario debe responder, indicando un asunto determinado o con un enlace en el propio mensaje que el usuario debe seguir. Esto asegura que el usuario es realmente el dueño de la dirección de correo indicada.
  • Es posible enviar notificaciones a los usuarios, o añadir opciones para restablecer o recordar contraseñas.
  • Hace más difícil que se repitan identificadores. En sistemas donde no se requiere una dirección de correo como identificador, es habitual que se tenga que probar varias veces hasta que se encuentra un identificador válido, que no haya sido usado por otra persona.

Como inconveniente, los usuarios que no tengan una cuenta de correo no podrán darse de alta. Aunque es relativamente sencillo conseguir una cuenta de correo gratuita, se considera una mala costumbre obligar a los usuarios a cumplir determinadas condiciones para acceder a algún servicio, y en determinados casos se puede considerar una técnica discriminatoria.

En cuanto a la autentificación, implica la consulta de los datos leídos dentro de algún tipo de base de datos. La elección de esa base de datos depende de nosotros. En sistemas flexibles, en los que se pueden asignar o eliminar usuarios de forma dinámica, se suele usar una base de datos como MySQL. Si se trata de un sistema más rígido, podemos optar por otro tipo de bases de datos, como simples ficheros de texto, o en el caso más simple posible, a la codificación dura de datos, es decir, con variables definidas en PHP.

Este último método no es demasiado práctico, ya que cualquier cambio en los datos de usuarios implica modificar el código y actualizarlo en el servidor.

En este artículo usaremos una base de datos MySQL, ya que hay algunos detalles que hay que tener en cuenta en ese caso particular.

Base de datos de usuarios

En  una  tabla  de  una  base  de  datos  almacenaremos los datos de los usuarios.  Para  este  ejemplo  nos  basta  con un identificador y una contraseña.

También se asume que la base de datos se llama «database» y la tabla «usuario», si fuera necesario, es fácil cambiar estos nombres.

Para  crear  la  tabla y añadir un par de usuarios para probar puedes ejecutar estas consultas SQL, dentro de la base de datos «database»:

----8<------
 CREATE TABLE IF NOT EXISTS `usuario`
 `iduser` int(11) NOT NULL AUTO_INCREMENT,
 `usuario` varchar(64) NOT NULL,
 `password` varchar(42) NOT NULL,
 PRIMARY KEY (`iduser`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

 INSERT INTO `usuario` (`usuario`,`password`)
 VALUES("prueba",PASSWORD("prueba"));

 INSERT INTO `usuario` (`usuario`,`password`)
 VALUES("ejemplo",PASSWORD("ejemplo"));
----8<------

Hay que destacar que no almacenaremos las contraseñas tal cual. Esto sería un problema de  seguridad,  ya que cualquiera que acceda a la base de datos, incluido el administrador, podría conocer todas las contraseñas.

Nota: aunque el administrador siempre tiene la posibilidad de saltarse esta medida no debemos menospreciar esta seguridad extra, ya que es frecuente que los usuarios usen las mismas contraseñas en varios lugares. Este sistema protege las contraseñas, aunque la cuenta no sea del todo privada para el administrador.

En  su  lugar,  lo  normal  es  almacenar un código HASH asociado a la contraseña.  MySQL  dispone de una función específica para eso, que se llama «PASSWORD». En cualquier caso, se puede usar cualquier otra función HASH, como MD5 o SHA.

La ventaja de estas funciones para este caso es que no son reversibles, es decir, no es posible obtener la contraseña a partir de su código HASH, o al menos no de una forma sencilla (siempre se puede usar la fuerza bruta).

Lo  que haremos para validar un usuario es verificar que la contraseña indicada tiene el mismo código HASH que está almacenado en la base de datos.

Formulario de entrada

Empecemos  con  la  página  de  entrada, «index.php», que mostrará el formulario de «login»:

 ----8<------
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<fieldset>
    <legend>Formulario de entrada</legend>
    <form method="POST" action="login.php">
        <br/>
        <label>Usuario: </label>
        <input type="text" name="usuario" size="40" value="">
        <br/>
        <br/>
        <label>Contraseña: </label>
        <input type="password" name="password" size="40" value="">
        <br/>
        <br/>
        <input type="submit" name="entrar" value="Entrar">
    </form>
</fieldset>
</body>
</html>
----8<------

Verificar usuario

Cuando   el  usuario  pulsa  en  «Entrar»  se  ejecuta  el  script  PHP «login.php», que verifica si los valores suministrados pertenecen a un usuario válido de la base de datos. Si es así, se abre una sesión y se almacenan un par de variables de sesión:

----8<------
<?php
$msUser = "tu usuario MySQL";
$msPassword = "tu contraseña MySQL";
$IdConexion = mysql_connect('localhost', $msUser, $msPassword)
or die ('Imposible conectar con base de datos.');

$head = "Location: http:index.php";
if(isset($_POST["password"]) && isset($_POST["usuario"])) {
    if(isset($_POST["entrar"])) {
        $Query  = "SELECT * FROM database.usuario ";
        $Query .= "WHERE usuario=\"".$_POST["usuario"]."\" AND password=PASSWORD(\"".$_POST["password"]."\")";
        $IdConsulta = mysql_query($Query, $IdConexion);
        if((mysql_num_rows($IdConsulta) > 0)) {
            session_start();
            $_SESSION["autentificado"]= true; 
            $_SESSION["usuario"] = mysql_fetch_array($IdConsulta);
            $head = "Location: http:index2.php";
        }
    }
    mysql_close($IdConexion);
}
header($head);
?>
----8<------

Si  la  consulta tiene éxito, creamos dos variables de sesión. En una guardamos los datos del  usuario, y en la otra un bit que indica si está conectado o no.

Una  vez  creada  las  variables de sesión,  almacenamos la página que se abrirá a continuación  en  $head,  que  será  «index2.php». Si el usuario no es
válido,  $head  contienen  el  valor  inicial, que es la página con el formulario de entrada.

Al  finalizar,  indicamos  que  se cargue la página $head, mediante la directiva header().

Si  el  usuario  no  existe  o la contraseña no es válida, volvemos al formulario  de  entrada. Si existe, se visualiza  «index2.php», que de momento sólo muestra el formulario de salida.

Mecanismo de protección de contenidos

En cada página con acceso restringido tenemos que incorporar un mecanismo que evite que la página sea mostrada si el usuario no ha hecho login correctamente.

----8<------
<!DOCTYPE HTML>
<?php
    $msUser = "tu usuario MySQL";
    $msPassword = "tu contraseña MySQL";
    $IdConexion = mysql_connect('localhost', $msUser, $msPassword)
    or die ('Imposible conectar con base de datos.');

    session_start();
    if(isset($_SESSION["autentificado"]) && $_SESSION["autentificado"]) {
        $Query = "SELECT * FROM database.usuario WHERE iduser=\"".$_SESSION["usuario"]["iduser"]."\"";
        $IdConsulta = mysql_query($Query, $IdConexion);
        // Si el usuario ya no está en la base de datos, hacer logout:
        if(mysql_num_rows($IdConsulta) == 0) {
            // Logout:
            $_SESSION["autentificado"] = false; 
            session_destroy();
        }
    } else {
        // Logout:
        $_SESSION["autentificado"] = false; 
        session_destroy();
    }

    mysql_close($IdConexion);

    if(!$_SESSION["autentificado"]) {
        header("location: http:index.php");
        exit(0);
    }
?>
<html>
<head>
</head>
<body>
    <h1>Usuario <?php echo $_SESSION["usuario"]["usuario"]; ?> ingresado con éxito.</h1>
    <fieldset>
    <legend>Salir</legend>
        <form method="POST" action="logout.php">
        <br/>
        <input type="submit" name="salir" value="Salir">
        </form>
    </fieldset>
</body>
</html>
----8<------

El código PHP anterior al tag <html> verifica si el usuario tiene abierta una sesión. Si es así, no tiene ningún efecto, y el resto de la página se carga correctamente. Si el usuario no tiene una sesión abierta, o ha sido borrado desde que la abrió, la página no sigue cargando, y en su lugar se muestra de nuevo la página de «login».

Es importante verificar si el usuario sigue estando en la base de datos. Supongamos que el administrador elimine a un usuario que está actualmente conectado; si no verificamos esta condición, mantendría su acceso hasta que cierre la sesión, que es algo que normalmente no nos interesa.

Este  ejemplo es simple, y  sólo  muestra un formulario de salida, si se pulsa «Salir» se ejecuta el script «logout.php».

----8<------
 <?php
    session_start();
    $_SESSION["autentificado"] = false;
    session_destroy();
    header("Location: http:index.php");
?>
----8<------

Modificamos el valor del bit que indica que el usuario está conectado. Cerramos la sesión, y cargamos la página de «login».

En casos más sofisticados podemos añadir a la base de datos tablas que permitan dar acceso a determinadas zonas de la página, definiendo niveles de acceso, o estableciendo permisos para determinadas áreas.

El administrador, o los usuarios con el nivel de acceso adecuado podrá otorgar o denegar el acceso de cada usuarios a cada zona.

También, como medida de seguridad adicional, es interesante limitar el tiempo de vida de la sesión, de modo que si un usuario abandona la página sin hacer «logout», su sesión se cierre automáticamente al cabo de unos minutos. El tiempo de fin de sesión se puede restablecer cada vez que se cargue una página. Esto evita que otras personas puedan acceder a la página con la cuenta de ese usuario desde el mismo ordenador. El inconveniente es que, si el usuario se demora mucho tiempo en una página, por ejemplo, completando un formulario, al seguir navegando se haya cerrado la sesión, y se pierdan los datos introducidos. Hay que medir bien estos tiempos, o al menos avisar al usuario en determinadas páginas del tiempo del que dispone para completar la tarea.

 

Imágenes dinámicas en PHP

Paleta de coloresPHP dispone de varias extensiones para el tratamiento y generación de imágenes.

En esta entrada usaremos la extensión GD que permite crear y manipular imágenes en varios formatos, como GIF, PNG, JPEG, SWF, TIFF y JPEG2000.

Las utilidades de crear imágenes usando PHP son muchas. Yo, por ejemplo, las he usado para crear contadores o imágenes de botones y menús, que se pueden personalizar en función de opciones del usuario.

Crear imágenes

Un fichero php de imagen se comporta exactamente igual que una imagen. Por ejemplo, podemos insertar una imagen dentro de una página usando la etiqueta img, de cualquiera de estos modos:

<img src="imagen.jpg" width="800" height="600" alt="imagen">
<img src=imagen.php width="800" height="600" alt="imagen">

En el primer caso se muestra la imagen almacenada el el fichero «imagen.jpg». En el segundo se muestra la imagen generada por el script «imagen.php».

Lo que sigue es un ejemplo de imagen generada en php:

<?php
    // Representación de funciones
    // Mayo de 2013, Con Clase
    // Salvador Pozo
    header('Content-type: image/png');

    $im = ImageCreateTrueColor(640, 480);
    $azul = ImageColorAllocate($im, 128, 128, 255);
    $azul2 = ImageColorAllocate($im, 64, 64, 230);
    $verde = ImageColorAllocate($im, 0, 64, 0);
    $verde2 = ImageColorAllocate($im, 0, 200, 0);

    // Ejes de coordenadas:
    ImageLine($im, 0, 240, 640, 240, $verde2);
    ImageLine($im, 320, 0, 320, 480, $verde2);

    // Lineas de escala:
    for($i = 50; $i < 320; $i+=50) {
        ImageLine($im, 320+$i, 0, 320+$i, 480, $verde);
        ImageLine($im, 320-$i, 0, 320-$i, 480, $verde);
    }

    for($i = 50; $i < 240; $i+=50) {
        ImageLine($im, 0, 240+$i, 640, 240+$i, $verde);
        ImageLine($im, 0, 240-$i, 640, 240-$i, $verde);
    }

    $y0 = $y1 = 240;
    $x0 = $x1 = 0;
    for($x1 = 0; $x1 < 640; $x1++) {
        $y1 = 240 + 200*sin($x1/25);
        ImageLine($im, $x0, $y0, $x1, $y1, $azul);
        $x0 = $x1;
        $y0 = $y1;
    }

    $y0 = $y1 = 240;
    $x0 = $x1 = 0;
    for($x1 = 0; $x1 < 640; $x1++) {
        $y1 = 240 + 150*cos($x1/35);
        ImageLine($im, $x0, $y0, $x1, $y1, $azul2);
        $x0 = $x1;
        $y0 = $y1;
    }

    ImagePng($im);
    ImageDestroy($im);
?>

Gráfica PHPLo primero que se debe hacer es enviar una cabecera con el tipo de contenido de la imagen. En este caso se trata de una imagen en formato png, pero se pueden generar otros formatos, como gif o jpg.

El segundo paso consiste en crear un recurso de imagen, usando la función ImageCreateTrueColor, especificando las dimensiones de la imagen en pixels en los dos argumentos, ancho y alto. También podemos crear recursos de imagen a partir de imágenes existentes, usando funciones como ImageCreateFromJpeg o ImageCreateFromGif, por ejemplo. En esos casos no es necesario especificar dimensiones, sólo la ruta de la imagen a usar como fuente.

En este ejemplo hemos creado cuatro plumas de cuatro colores diferentes, usando ImageColorAllocate. En esta función, el primer argumento es el recurso de imagen que hemos creado antes, y los tres restantes definen los componentes rojo, verde y azul, respectivamente. Se puede usar la función ImageColorAllocateAlpha, y añadir un quinto argumento, con la componente de transparencia, α.

A continuación trazamos el dibujo deseado, la extensión gd proporciona varias funciones para trazar líneas, arcos, elipses, rectángulos, etc. Para más detalles, consulta la documentación de php.

Parametrizar imágenes

Una ventaja de generar nuestras propias imágenes es que podemos parametrizarlas.

Veamos, como ejemplo, cómo crear una imagen para un contador. Pasaremos como parámetros al script la longitud, en número de dígitos, el valor del contador, el tamaño y el color.

<img src=contador.php?con=1234&lon=6&tam=20&col=114422 width="111" height="24" alt="1234">

El código para este script puede tener esta forma:

<?php
    // Genera imagen de contador
    // lon: longitud en dígitos
    // con: valor del contador
    // tam: tamaño de la fuente
    // col: color en hexadecimal: rrggbb
    // Mayo de 2013, Con Clase
    // Salvador Pozo

    Header("Content-type: image/png");

    if(isset($_GET["lon"])) {
      $lon = (int)$_GET["lon"];
    } else $lon = 4;
    if(isset($_GET["con"])) {
      $con = (int)$_GET["con"];
    } else $con = 0;
    if(isset($_GET["tam"])) {
      $tam = (int)$_GET["tam"];
    } else $tam = 15;
    if(isset($_GET["col"])) {
      $col = "0x".$_GET["col"];
    } else $col = "0x00c000";

    $b = (0+$col) % 256;
    $col = ($col-$b)/256;
    $g = $col % 256;
    $r = ($col-$b)/256;

    $contador = sprintf("%0".$lon."d", $con);
    $fuente = './fuentes/ariblk.ttf';
    $box = imageftbbox($tam , 0 , $fuente, $contador);
    $x = $box[2]-$box[0]+4;
    $y = $box[3]-$box[7]+4;
    $im = imagecreatetruecolor($x, $y);
    $azul = ImageColorAllocate($im, 128, 128, 255);
    $verde = ImageColorAllocate($im, 0, 64, 0);
    $verde2 = ImageColorAllocate($im, $r, $g, $b);

    imageline($im, 0,0,$x-1,0,$azul);
    imageline($im, 0,0,0,$y-1,$azul);
    imageline($im, 0,$y-1,$x-1,$y-1,$azul);
    imageline($im, $x-1,$y-1,$x-1,0,$azul);
    imageline($im, 1,1,$x-2,1,$verde);
    imageline($im, 1,1,1,$y-2,$verde);
    imageline($im, 1,$y-2,$x-2,$y-2,$verde);
    imageline($im, $x-2,$y-2,$x-2,1,$verde);

    $box = imageftbbox($tam , 0 , $fuente, "1");
    imagettftext($im, $tam, 0, -1, -$box[5]+1, $verde2, $fuente, $contador);

    ImagePng($im);
    ImageDestroy($im);
?>

Contador PNGEl resultado es mejorable, por supuesto. Puedes comprobar que para diferentes tamaños los dígitos no se ajustan bien al marco. Esto es debido a pequeñas diferencias en las anchuras de los caracteres, y al modo en que se trazan los textos, ya que las coordenadas se refieren a la esquina inferior izquierda del texto, en lugar de a la superior izquierda.

Para este ejemplo se ha usado una fuente truetype llamada ‘ariblk.ttf’, que se almacena en una carpeta local llamada fuentes. Puedes usar una fuente de sistema o usar la fuente que prefieras copiándola a la carpeta adecuada.

Modificar imágenes

Otro uso habitual consiste en modificar imágenes para añadir efecto o insertar marcas de agua. Una marca de agua es una segunda imagen que se superpone con la que queremos modificar, pero intentando que no dificulte demasiado su visión. Son difíciles de eliminar, y se suelen usar para evitar que las imágenes sean usadas por terceras personas. Generalmente se añade un logotipo o un texto que identifica al propietario de la imagen.

<img src="marcaagua.php?img=imagen.jpg" width="640" height="480" alt="imagen">

En este ejemplo, redimensionaremos la imagen a 640×480 pixels y añadiremos un texto superpuesto.

<?php
    // Marcas de agua
    // Mayo de 2013, Con Clase
    // Salvador Pozo
    header('Content-type: image/jpg');
    $nombre_archivo = $_GET["img"];
    $im = ImageCreateTruecolor(640, 480);
    $im1 = ImageCreateFromJpeg($nombre_archivo);
    list($ancho, $alto) = GetImageSize($nombre_archivo);
    ImageCopyResized  ( $im, $im1, 0, 0, 0, 0, 640, 480, $ancho, $alto ); 
    ImageDestroy($im1);

    $color = ImageColorAllocateAlpha($im, 255, 255, 255, 110);
    $fuente = './ariblk.ttf';
    ImageTtfText($im, 80, 30, 50, 400, $color, $fuente, "Con Clase");
    ImageJpeg($im);
    ImageDestroy($im);
?>

Marca de aguaMarca de agua + B/NEl resultado es un tamaño de imagen predecible, con una marca de agua. Sin embargo, debemos tener cuidado con las proporciones de la imagen original, ya que al redimensionarla puede quedar distorsionada.

Se pueden modificar imágenes usando la función ImageFilter. En este caso deberemos tener la precaución de verificar si la función existe en nuestra versión de PHP, ya que no siempre es así:

if(function_exists('ImageFilter'))
{
    ImageFilter($im, IMG_FILTER_GRAYSCALE);
}

Este código, por ejemplo, convierte la imagen $im a escala de grises.  Puedes consultar la documentación para ver los filtros disponibles.

Imágenes en hojas de estilo

Es posible crear hojas de estilo en las que usaremos imágenes de fondo para algunos elementos, como títulos, tablas, etc.

Esas imágenes pueden ser generadas mediante código PHP, y la hoja de estilo completa, también.

Empecemos por una página en la que se cargue una hoja de estilo generada mediante PHP:

<!DOCTYPE HTML>
<html>
<head>
   <link rel="StyleSheet" href="ejemplo.css.php?c1=002080&amp;c2=001040&amp;c3=0040c0&amp;c4=00ff00" 
       title="Estilo dinamico" media="Screen" type="text/css"/>
</head>
<body>
<h1>Titulo</h1>
<p>Texto...</p>
</body>
</html>

Sólo definiremos un estilo para la etiqueta H1, por eso el código de la página sólo muestra un título, y un pequeño texto.

Lo importante aquí es que la hoja de estilo se carga desde un script PHP llamado «ejemplo.css.php». Le pasamos cuatro parámetros, con los colores que deseamos.

El código de la hoja de estilos es:

<?php
    // Hoja de estilo dinámica
    // Mayo de 2013, Con Clase
    // Salvador Pozo

    header("Content-type: text/css");
    $Col1 = $_GET["c1"];
    $Col2 = $_GET["c2"];
    $Col3 = $_GET["c3"];
    $Col4 = $_GET["c4"];

    print("h1 { background-image: ");
    print("url(h1.php?c1=".$Col1."&c2=".$Col2."&c3=".$Col3."&c4=".$Col4."); ");
    print("background-repeat: repeat-x; background-position: 0 0; ");
    print("height:28px; font-size:22px; font-weight:normal; padding-left: 50px; overflow:hidden; color: #".$Col4."; }\n");

?>

Ahora personalizamos la etiqueta «h1», usando una imagen de fondo generada mediante un script PHP, tal como hicimos con otras imágenes anteriores.

Con los parámetros indicados, la hoja de estilos tiene el siguiente contenido:

h1 { background-image: url(h1.php?c1=002080&c2=001040&c3=0040c0&c4=00ff00); 
background-repeat: repeat-x; 
background-position: 0 0; 
height:28px; 
font-size:22px; 
font-weight:normal; 
padding-left: 50px; 
overflow:hidden; 
color: #00ff00; 
}

Hay que prestar especial atención a la primera línea:  header(«Content-type: text/css»). Esta es la que define el tipo de salida que genera PHP, en este caso, una hoja de estilo.

Para finalizar, el código PHP para h1.php es el siguiente:

<?php
    // Imagen de fondo para H1 dinámica
    // Mayo de 2013, Con Clase
    // Salvador Pozo

    function CrearColor($im, $color) {
         $r = HexDec(substr($color, 0, 2));
         $g = HexDec(substr($color, 2, 2));
         $b = HexDec(substr($color, 4, 2));
       return ImageColorAllocate($im, $r, $g, $b);
    }

    Header("Content-type: image/gif");

    $im = imagecreatetruecolor(1, 25);

    $col = array();
    $col[0] = CrearColor($im, $_GET["c1"]);
    $col[1] = CrearColor($im, $_GET["c2"]);
    $col[2] = CrearColor($im, $_GET["c3"]);
    $col[3] = CrearColor($im, $_GET["c4"]);

    imageline($im, 0, 0, 9, 0, $col[3]);
    for($i = 0; $i < 6; $i++) {
         imageline($im, 0, 1+$i*4, 9, 1+$i*4, $col[1]);
         imageline($im, 0, 2+$i*4, 9, 2+$i*4, $col[0]);
         imageline($im, 0, 3+$i*4, 9, 3+$i*4, $col[0]);
         imageline($im, 0, 4+$i*4, 9, 4+$i*4, $col[2]);
    }
    imageline($im, 0, 24, 9, 24, $col[3]);

    ImageGif($im);
    ImageDestroy($im);
?>

Salida en navegadorLa gran ventaja: que se pueden generar todos los gráficos asociados a una página con diferentes combinaciones de colores, sin tener que crear ficheros de imágenes para cada caso.

Los colores que se usan en el fichero HTLM puede ser extraídos de una base de datos, o de un fichero de configuración. Las imágenes se generan de forma dinámica a partir de los scripts PHP y usando esos colores.

Otros usos

La generación dinámica de imágenes tiene otras muchas aplicaciones, por ejemplo, crear captchas. Es relativamente sencillo crear imágenes a partir de un texto, usando diferentes fuentes y colores, y distorsionando la salida, añadir fondos, ruido, etc.

Avatar generadoOtra aplicación es la creación de imágenes para usar como avatares. Probablemente has visto este tipo de avatares en foros y blogs. Se generan a partir de un código HASH, creado a su vez a partir de una dirección de correo o una IP.

También se puede usar la generación dinámica para crear códigos QR conclase.netQR, como el de la izquierda, aunque esto resulta algo más complicado, y no por el diseño del gráfico, sino por los algoritmos que se usan para generarlos.

Clase PHP para MySQL

Base de datpsEn las últimas versiones de PHP, el soporte para programación orientada a objetos mejoró considerablemente. A día de hoy, que cualquier proyecto medianamente complejo desarrollado en PHP esté basado en POO es prácticamente una necesidad.

En nuestros proyectos, pequeños o grandes, usar clases es una gran ventaja. Sobre todo si las diseñamos con cierta perspectiva y pensando en su reutilización. Empezamos ahora una serie de artículos sobre el encapsulamiento de algunas herramientas que usaremos frecuentemente en proyectos PHP. La idea es mejorar nuestro rendimiento y el de nuestras páginas, reutilizando códigos tan optimizados como seamos capaces de escribir.

Aunque PHP ya tiene diseñadas clases para estas tareas (por ejemplo, mysqli), a veces es más cómodo usar las nuestras. Ya sé que esto está en contra de la reutilización, y que es «reinventar la rueda». Teniendo todo esto en cuenta, consideremos esto como un ejercicio. 🙂

Tanto más si tenemos en cuenta las recomendaciones que aparecen en la documentación de PHP:

Esta extensión está obsoleta a partir de PHP 5.5.0 y no está recomendada para escribir código nuevo, ya que será eliminada en el futuro. En su lugar, se debería utilizar la extensión mysqli o PDO_MySQL.

Al final veremos como usar la clase mysqli, en lugar de la nuestra.

Nuestras clases para bases de datos

Empecemos con un par de clases para acceder a bases de datos MySQL.

<?php
    /*  Clase mySQLDB para acceder a bases de datos mySQL.
        Clase queryDB para realizar consultas en una base de datos.
    */
    class mySQLDB {
        const servidor = 'host'; /* Sustituir por valor adecuado */
        const usuario  = 'user'; /* Sustituir por valor adecuado */
        const clave    = 'password'; /* Sustituir por valor adecuado */
        protected $IdConexion;
        protected $basedatos;

        function __construct() {
            $this->IdConexion = @mysql_connect(self::servidor, self::usuario, self::clave) 
                or die ('Imposible conectar con base de datos.');
        }

        function __destruct() {
            @mysql_close($this->IdConexion);
        }

        function SetBaseDatos($nombrebd) {
            $this->basedatos = $nombredb;
            @mysql_select_db( $nombredb, $this->IdConexion);
        }

        function GetConexion() {
            return $this->IdConexion;
        }

        function GetBaseDatos() {
            return $this->basedatos;
        }
    }

    class queryDB {
        protected $BD;
        protected $Id;

        function __construct( $Bd, $Query ) {
            $this->BD = $Bd;
            $this->Id = @mysql_query( $Query, $this->BD->GetConexion() );
        }

        function __destruct() {
            @mysql_free_result($this->Id);
        }

        function Cerrar() {
            @mysql_free_result($this->Id);
        }
        function NumRows() {
            return @mysql_num_rows($this->Id);
        }

        function FetchArray() {
            return @mysql_fetch_array($this->Id);
        }
    }
?>

En la definición de las clases anteriores quiero llamar la atención sobre algunas cosas.

  1. El carácter ‘@’ antes de las llamadas a las funciones ‘mysql_’ es para evitar que se muestren los mensajes de aviso que se producen cuando se usan funciones obsoletas. Antes he mencionado que la extensión «mysql» está considerada obsoleta a partir de PHP 5.5.
  2. En el constructor de la base de datos incluimos el código necesario para establecer la conexión, y en el destructor, cerramos esa conexión. Esto libera a nuestra aplicación de la tarea de abrir y cerrar conexiones a bases de datos, ya que estas se realizan de forma automática.
  3. SetBaseDatos asigna una base de datos por defecto a la conexión. Podremos cambiar esta base de datos cuando queramos, o ignorarla, incluyendo el nombre completo de la tabla en las consultas, basededatos.tabla.
  4. GetBaseDatos permite recuperar el nombre de la base de datos por defecto.
  5. GetConexion nos devuelve el identificador de la conexión asociada a la base de datos, aunque probablemente nunca necesitemos usarla.
  6. En este ejemplo he optado por crear clases separadas para bases de datos y consultas, aunque se podrían haber creado ambas usando una única clase.
  7. El constructor de la clase queryDB almacena una variable con la base de datos y un identificador a la consulta creada. Para ello se necesitan dos parámetros, que son la base de datos y la cadena con la consulta.
  8. El destructor libera los resultados asociados a la consulta.
  9. El método Cerrar nos permite liberar los resultados de la consulta cuando sea necesario. Por ejemplo, no será posible hacer una consulta en una base de datos hasta que la anterior haya sido cerrada. Si hacemos dos consultas en el mismo método será necesario cerrar la primera antes de hacer la segunda.
  10. NumRows recupera el número de filas que contiene el resultado de la consulta.
  11. FetchArray recupera una fila o tupla de la consulta.

Usar estas clases en una aplicación es muy sencillo. Por ejemplo:

    $database = new mysqlDB();
    $Consulta = new queryDB( $database, "SELECT * FROM agenda.personas ORDER BY nombre");
    print("<table>\n");
    print("<tr><th>Nombre</th><th>Fecha</th></tr>\n");
    for( $i = 0; $i < $Consulta->NumRows(); $i++) {
        $reg = $Consulta->FetchArray();
        print("<tr><td>".$reg["nombre"]."</td><td>".$reg["fecha"]."</td></tr>\n");
    }
    $Consulta->Cerrar();
    print("</table>\n");

Esta es una de muchas posibles implementaciones, simplificada a propósito, ya que no proporciona soporte para la mayor parte de las funciones de la extensión mysql. Sin embargo, resulta bastante útil, y es una buena muestra de las ventajas de encapsular que proporciona la programación orientada a objetos.

Usando la clase Mysqli

PHP dispone de una extensión para acceso a MySQL que en la documentación se denomina «extensión MySQL mejorada».

Consta no de dos clases, sino de seis, aunque tres de ellas son de utilidad limitada para la mayoría de las aplicaciones: mysqli_driver, mysqli_warning y mysqli_sql_exception.

Nos centraremos en las otras tres.

Para ver una documentación completa de estas clases recomiendo acceder a la página de PHP.

La clase mysqli

El constructor tiene cierto parecido al nuestro, los tres primeros parámetros son los mismos. Se puede añadir un cuarto, con el nombre de la base de datos por defecto, un quinto con el puerto de conexión y un sexto con el socket. Aunque los seis parámetros son opcionales, seguramente querremos usar los tres o cuatro primeros siempre. Por supuesto, este constructor crea o intenta crear la conexión con la base de datos.

El método query permite realizar consultas. El primer parámetro es la cadena con la consulta, el segundo es opcional, y permite seleccionar el modo en que se trata el resultado. Por defecto es MYSQL_STORE_RESULT, que indica que el resultado será almacenado. Si se usa MYSQL_USE_RESULT los resultados de la consulta no serán almacenados. Esto es poco aconsejable si se realizan muchas actualizaciones desde el lado del cliente, ya que las tablas consultadas se bloquean.

El método select_db permite seleccionar una base de datos por defecto.

Otro dato miembro interesante es $insert_id, que contiene el identificador de la última fila insertada en una columna autoincrementada.

La clase mysqli_result

El método query devuelve un objeto de la clase mysqli_result. En esta clase hay, entre otros, los siguientes datos y métodos:

El dato $num_rows contiene el número de filas de un resultado.

El método fetch_array obtiene una fila desde el conjunto de resultados.

El método free (o sus alias close y free_result) liberan la memoria asociada al resultado.

La clase mysqli_smtp

Esta clase se usa para realizar las consultas usando otro sistema, que requiere la preparación de la consulta, asociar columnas con variables, etc.

Será un tema para tratar en otra entrada del blog.

Ejemplo usando mysqli

    $database = new mysqli("localhost", "user", "password");
    $Resultado = $database->query( "SELECT * FROM agenda.personas ORDER BY nombre");
    print("<table>\n");
    print("<tr><th>Nombre</th><th>Fecha</th></tr>\n");
    for( $i = 0; $i < $Resultado->num_rows; $i++) {
        $reg = $Resultado->fetch_array();
        print("<tr><td>".$reg["nombre"]."</td><td>".$reg["fecha"]."</td></tr>\n");
    }
    $Resultado->free();
    print("</table>\n");                                    

Como se puede apreciar, los ejemplos son muy parecidos.