Sentencias preparadas de MySQL en PHP (ejemplos estructurados)

martes 12 julio 2011 | In English

Ejemplos actualizados el 23 de julio de 2011

Encuentro triste cuantos sistemas en linea hay desprotegidos aún contra el tipo de ataque más simple, inyecciones de SQL. Esta es una guía y un conjunto de ejemplos sobre el uso de sentencias preparadas de MySQL en PHP. Si quieres ver como un ataque de inyección de SQL funciona puedes leer el artículo previo Un ataque de inyección de SQL. Hay una guía similar a esta pero utilizando la interfase orientada a objetos de MysqlI en los ejemplos en Sentencias preparadas de MySQL en PHP (ejemplos orientados a objetos).

Temas
Verificando que MysqlI esté disponible
Como usar esta guía
Descarga los ejemplos
Crear un objeto de mysqli y conectarse a la base de datos
Hacer una petición simple
Recuperar datos de una petición simple
Otras funciones útiles
Como usar sentencias preparadas con MysqlI
Recuperar la información devuelta tras ejecutar una sentencia preparada
Otras funciones simples
Notas al pie
Artículos relacionados
Referencias

Verificando que MysqlI esté disponible

MysqlI (MySQL Improved Extension/Extensión mejorada de MySQL) nos permite usar características y funcionalidad provista por MySQL 4.1 y superior. Esta extensión es incluida con la versión 5 y superior de PHP, por lo tanto todos los ejemplos de esta guía requiere al menos la versión 5 de PHP. Si no tienes al menos esta versión de PHP, no podrás aprovechar las sentencias preparadas. Por supuesto que puedes proteger tus sistemas contra ataques de inyección de SQL, aunque solo publicaré una guía para versiones de PHP inferiores a la 5 si hay peticiones para hacerlo, tengo el código, no estoy seguro si una guía para esa versión de PHP sigue siendo relevante. Una versión de MySQL superior a la 4.1 es también necesaria (4.1.3+ es recomendada).

Algunas ventajas que nos ofrece MysqlI sobre la librería más vieja MySQL son:

  • Una interfase orientada a objetos (Esta guía cubre eso).
  • Soporte para sentencias preparadas
  • Soporte para sentencias múltiples
  • Soporte para transacciones (aunque no las usaré en estas guías)

Si tienes acceso al comando PHP en la computadora donde PHP está instalado, puedes revisar el soporte usando el comando:

php --info | grep "MysqlI Support"

Esto te mostrará MysqlI Support => enabled si MysqlI esta disponible.

Si no tienes acceso al comando PHP, puedes crear un nuevo archivo de PHP con el siguiente contenido:

<?php
phpinfo();
?>

Guarda el archivo, ponlo en el servidor, y accesa al archivo desde un navegador, esto te mostrará toda la información sobre PHP. Busca la sección de MysqlI.

Una vez que estás seguro que el soporte de MysqlI está activado, puedes descargar los ejemplos y continuar con la guía.

Como usar esta guía

Esta guía explica las funciones y la lógica de usar sentencias preparadas. Los ejemplos contienen código funcional mostrando su uso. Los ejemplos contienen la inserción, edición, borrado y mostrado de información en una tabla de la base de datos, así como un script para crear la tabla que utilizan los ejemplos.

Necesitaras la información para conectarse a la base de datos (nombre de usuario, contraseña, servidor) y una base de datos con la cual jugar. Llena esta información en el archivo ajustes.php, entonces abre el archivo mysql_php_crear_tabla.php en el navegador, este archivo creara una tabla y la llenará con algo de información. Además contiene vínculos para ver los otros ejemplos funcionando.

Descarga los ejemplos

mysqli_php_estructurados.tar.gz
Tamaño: 6927 bytes
MD5: 3e9b06a2e8f1010b02c3a0bfdbb50777
SHA1: b974329e5f14d14873c5c6a13841c933a06fd4ca
Licencia: Similar a BSD-3

La razón por la que estoy agregando una licensia similar a BSD-3 es la descarga de responsabilidad, honestamente no me importa si el código es usado, seré feliz si lo es. Sin embargo, si los ejemplos son redistribuidos o republicados me gustaría un vínculo hacia mi sitio web.

Crear un objeto de mysqli y conectarse a la base de datos

Los cuatro parámetros que necesitamos para conectarnos a la base de datos son:

  • Servidor - La dirección del servidor donde reside la base de datos, en muchos hospedajes esto es usualmente localhost.
  • Nombre de usuario - Un nombre de usuario válido para la base de datos, este usuario debe de contar con los permisos necesarios para realizar las operaciones requeridas en la base de datos. Es altamente recomendable crear un usuarios secundario además del administrador, con solo los permisos necesarios requeridos para la operación del sistema, y usar este usuario secundario para los programas de PHP, mientras se mantiene al usuario administrativo para, bueno, administrar la base de datos.
  • Contraseña - La contraseña de dicho usuario.
  • Base de datos - Este parámetro es opcional, puedes conectarte a la base de datos con solo los parámetros previos y una vez conectado seleccionar una base de datos a usar, sin embargo, yo uso este parámetro desde la función de conectarse para ahorrar unas lineas de código y la operación de seleccionar la base de datos.

La función para conectarse a la base de datos es mysqli_connect():

$link = mysqli_connect("localhost", "usuario", "contraseña", "nombre_base_de_datos");

Esta función regresa un vínculo identificador que usamos para manejar todas las peticiones subsecuentes a la base de datos.

Después de esto revisamos si la conexión a la base de datos tuvo éxito, o si no mostramos un mensaje de error y salimos del programa:

if (!$link) {
    die("No se puede conectar a la base de datos: " . mysqli_connect_error());
}

Todas las peticiones puedes ser hechas tras estas lineas. Una vez que terminamos con todas las peticiones de MySQL, debemos cerrar la conexión a la base de datos usando:

$mysqli_close($link);

Hacer una petición simple

Llamo peticiones simples a las que no involucran ningún tipo de entrada desde el usuario, o cualquier información sobre la que no tenemos control completo (como información guardada en las computadoras de usuarios en forma de cookies). Para realizar una petición simple solo usa la función mysqli_query(), esto probablemente no necesite ninguna explicación, un ejemplo de esto es:

$peticion = "select * from `jveweb_net_tabla_prueba`";
mysqli_query($link, $peticion);

Hice una lista de peticiones comunes en mi publicación previa Manejando MySQL desde la linea de comandos, principalmente para referencia personal, pero espero que otros también la encuentren útil.

Recuperar datos de una petición simple

Muchas de las peticiones que hacemos a la base de datos nos devolverán información. Estas peticiones nos regresan información en forma de una tabla.

Una vez que tenemos el resultado de la petición, la recuperamos usando las funciones mysqli_fetch_array(), mysqli_fetch_assoc() o mysqli_fetch_row(), llamar a cualquiera de estas funciones una vez recuperará el primer elemento en la tabla que obtuvimos como resultado de nuestra petición, y nos proveerá un arreglo con los valores. Si la tabla contiene más de una fila, cada vez que llamamos a cualquiera de estas funciones obtendremos la siguiente fila disponible en la tabla resultante, por lo tanto deberemos llamarla hasta que hayamos recuperados cada elemento en la tabla.

Por ejemplo, imaginemos que tenemos esta tabla:

campo_id campo_nombre campo_direccion campo_correo
1 Juan 140 Calle Cualquiera, Ciudad Cualquiera juan@jveweb.net
2 Valencia 72 Paseo Cualquiera, Ciudad Cualquiera valencia@jveweb.net
3 Rodrigo 23 Alguna Calle, Ciudad Cualquiera rodrigo@jveweb.net
4 Cristal 42 Calle X, Ciudad Cualquiera cristal@jveweb.net
5 Maria 763 Paseo X, Ciudad Cualquiera maria@jveweb.net
6 Carlos 21 Avenida Cualquiera, Ciudad Cualquiera carlos@jveweb.net

mysqli_fetch_array() puede darnos un arreglo numérico, un arreglo asociativo o ambos:

mysqli_fetch_array($resultado, MYSQLI_ASSOC); or mysqli_fetch_assoc($resultado); asociaría el nombre de el campo con el arreglo y nos daría:

Array
(
    ["campo_id"]        => 1
    ["campo_nombre"]    => "Juan"
    ["campo_direccion"] => "140 Random Street, Random City"
    ["campo_correo"]    => "juan@jveweb.net"
)

Mientras que mysqli_fetch_array($resultado, MYSQLI_NUM); o mysqli_fetch_row($resultado); asociaría el número de la columna con el arreglo y nos daría:

Array
(
    [1] => 1
    [2] => "Juan"
    [3] => "140 Random Street, Random City"
    [4] => "juan@jveweb.net"
)

Y por supuesto mysqli_fetch_array($resultado, MYSQLI_BOTH); nos daría un arreglo con 8 elementos, lo que nos daría ambos arreglos para que los usemos como deseamos, este es el comportamiento predeterminado.

Resumiendo, el código para recuperar esta información de la base de datos sería como sigue:

// Capturar el resultado de la petición en la variable $resultado
$resultado = mysqli_query( [vínculo identificador], [petición]);
if (!$resultado) {
    die("No se puede ejecutar la petición" . mysqli_error());
}

// Recuperar las filas en la tabla resultante hasta haber obtenido todas
while ($fila = mysql_fetch_array($resultado) {
    echo $fila[nombre del campo];
    echo $fila[nombre del campo];
}

Otras funciones útiles

Dos funciones más que nos permiten recuperar algo de información de la última petición usada son mysqli_insert_id() y mysqli_affected_rown()

mysqli_insert_id([vínculo identificador]);

Esta función nos da la ID de el último registro que fue insertado. El único parámetro es el vínculo identificador

mysqli_affected_rows([link identifier]);

Esta función nos dirá cuantas filas fueron afectadas por la última petición que realizamos. Su único parámetro es el vínculo identificador.

Como usar sentencias preparadas con MysqlI

Si nuestra petición a la base de datos contiene información que el usuario del sistema nos provee, o cualquier información que fue guardada fuera de nuestro control, como información guardada en cookies (la información de sesión usualmente se guarda de esta manera, por lo tanto puede ser alterada), las sentencias preparadas de SQL nos permites comunicarnos con la base de datos de manera segura, ya que separa la lógica de los datos que enviamos a la base de datos.

Mientras que en una petición de SQL simple solo necesitamos crear la petición incluyendo todos los datos que necesitan ser usados, como la ID de el registro con el que necesitamos trabajar o la información a ser insertada o actualizada, en una sentencia preparada creamos esta petición marcando los espacios donde los datos serán dados con signos de interrogación de cierre, y después ligamos las variables a estos espacios marcados. Un ejemplo explicará mejor esto que palabras, primero que nada, creamos una sentencia preparada de SQL:

$peticion = "insert into `jveweb_net_tabla_prueba` (`campo_nombre`,`campo_correo`) values(?,?)";
$stmt = mysqli_prepare($link, $peticion);

Esta función nos devuelve un objeto sentencia que guardamos en stmt.

Una vez que la petición está preparada, necesitamos ligar las variables usando mysqli_stmt_bind_param(), esto lleva tres o más parámetros, el primero es el objeto sentencia, el segundo los tipos de variables en forma de una cadena (s para cadena, d para número), y a partir de la tercer variable, la lista de variables separadas por una coma. Veamos un ejemplo de esto:

$nombre = "Juan";
$correo = "juan@jveweb.net";
mysqli_stmt_bind_param($stmt,"ss",$nombre, $correo);

Y con las variables ligadas, podemos ejecutar la petición con mysqli_stmt_execute(). Cuando terminamos con la sentencia preparada la cerramos con mysqli_stmt_close() (esto siempre va antes de mysqli_close() ).

mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);

Veamos otro ejemplo, actualicemos dos registros con la misma sentencia preparada:

$peticion = "update `jveweb_net_tabla_prueba` set `campo_nombre`=?, `campo_correo`=? where `campo_id` = ?";
if ($stmt = mysqli_prepare($link, $peticion)) {

    // Creemos las variables
    $id = 2;
    $nombre = "Juan";
    $correo = "prueba@jveweb.net";

    // Ligamos las variables y ejecutamos la petición
    mysqli_stmt_bind_param($stmt,"ssd", $nombre, $correo, $id);
    mysqli_stmt_execute($stmt);

    // Ya que tenemos los parámetros ligados, podemos simplemente cambiar
    // los valores y ejecutarla de nuevo
    $id = 3;
    $nombre = "Valencia";
    $correo = "otro@jveweb.net";
    mysqli_stmt_execute($stmt);

    // Y ahora cerramos la petición
    mysqli_stmt_close($stmt);
}

Recuperar la información devuelta tras ejecutar una sentencia preparada

Al igual que las peticiones simples, las sentencias preparadas pueden devolvernos información, pero la manera de recuperar esta información tras ejecutar usa sentencia preparada es diferente a la de una petición simple.

Para recuperar información de una sentencia preparada necesitamos usar las funciones mysqli_stmt_bind_result() y mysqli_stmt_fetch(). No terminamos con arreglos cuando recuperamos esta información, en vez de esto creamos un conjunto de variables, las ligamos al resultado, y tras ejecutar mysqli_stmt_fetch() nuestras variables ligadas terminan con la información. Y, al igual que en las peticiones simples, necesitaremos llamar a mysqli_stmt_fetch() hasta haber obtenido todos los datos resultantes si hay más de una fila como resultado.

Hagamos una petición select:

$peticion = "select `campo_nombre`,`campo_correo` from `jveweb_net_tabla_prueba` where `campo_id` between ? and ?";
if ($stmt = mysqli_stmt_prepare($link, $peticion)) {

    // Ligar las variables y ejecutar la sentencia
    $inicio = 2;
    $fin = 4;
    mysqli_stmt_bind_param($stmt, "dd", $inicio, $fin);
    mysqli_stmt_execute($stmt);

    // Ligar el resultado y recuperar los datos
    $nombre = "";
    $correo = "";
    mysqli_stmt_bind_result($stmt, $nombre, $correo);
    while (mysqli_stmt_fetch($stmt)) {
        echo $nombre;
        echo $correo;
    }

    // Y cerramos la sentencia
    mysqli_stmt_close($stmt);
}

Ya que estamos pidiendo dos diferentes campos (campo_nombre y campo_correo), el primero será guardado en la primera variable que ligamos ($nombre) y por supuesto el segundo será guardado en la segunda variable que ligamos ($correo), el orden aquí es muy importante.

Otras funciones simples

Al igual que con las peticiones sencillas, en las sentencias preparadas también tenemos dos funciones que nos permiten recuperar algo de información de las últimas peticiones usadas: mysqli_stmt_insert_id() y mysqli_stmt_affected_rows()

mysqli_stmt_insert_id([objeto sentencia]);

Esta función nos da la ID de el último registro que fue insertado. El único parámetro es el vínculo identificador

mysqli_stmt_affected_rows([objeto sentencia]);

Esta función nos dice cuantas filas fueron afectadas por la última petición que realizamos. Su único parámetro es el vínculo identificador.

Notas al pie

Si encuentro algo para corregir, será corregido, en los ejemplos incluyo un vínculo a esta sección.

La otra guía es muy similar, solo que usa al objeto mysqli en lugar de funciones, hay algunas diferencias, pero son menores, sin embargo pensé que mezclar ambas en el mismo artículo o en los mismos ejemplos podría ser un poco confuso.

Artículos relacionados

Referencias

Categorías: MySQL, PHP, Programación