Sentencias preparadas de MySQL en PHP (ejemplos orientados a objetos)

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 las funciones estructurales regulares de la librería MysqlI en Sentencias preparadas de MySQL en PHP (ejemplos estructurados).

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
Una petición simple de SQL (la clase mysqli)
Recuperar datos desde una petición simple (la clase Mysqli_result)
Algunos valores útiles de la clase MysqlI
Como usar sentencias preparadas con MysqlI (la clase Mysqli_stmt)
Recuperar la información devuelta tras ejecutar una sentencia preparada
Algunos valores útiles de la clase Mysqli_stmt
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

mysql_php_orientado_objetos.tar.gz
Tamaño: 6902 bytes
MD5: 6171d3439d17c0c8e66ecfa55458ed9c
SHA1: 4e89ac3a5b452c5d27accdad99418a27dd064c01
Licencia: Similar a BSD-3

La razón por la que estoy agregando una licensia 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.

Para conectarse a la base de datos podemos usar los parámetros durante la creación del objeto mysqli:

$mysqli = new mysqli("localhost", "usuario", "contraseña", "nombre_base_de_datos");

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

if (mysqli_connect_errno()) {
    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 el método close():

$mysqli->close();

Una petición simple de SQL (la clase mysqli)

Llamo peticiones simples a las que no involucran ningún tipo de entrada de 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 debemos hacer uso del método query() de el objeto mysqli, esto probablemente no necesité ninguna explicación, un ejemplo de esto es:

$peticion = "select * from `jveweb_net_tabla_prueba`";
$mysqli->query($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 desde una petición simple (la clase Mysqli_result)

Muchas de las peticiones que le hacemos a la base de datos van a devolvernos información. Cuando las peticiones nos regresan información crean un nuevo objeto de clase mysqli_result. Este nuevo objeto contiene la información en la forma de una tabla, y los métodos necesarios para recuperar esta información. Un ejemplo de como obtener esta nuevo objeto sería usando:

$resultado = $mysqli->query($peticion);

Una vez que tenemos el nuevo objeto, recuperamos la información utilizando los métodos fetch_array(), fetch_assoc() o fetch_row() del objeto mysqli_result, llamando cualquiera de estos métodos una vez recuperará al primer elemento en la tabla que obtuvimos como resultado de nuestra petición, y nos proveerá de un arreglo con los valores. Si la tabla contiene más de una fila, cada vez que llamemos cualquiera de estos métodos obtendremos la siguiente fila disponible en la tabla resultante, por lo tanto necesitaremos llamarlo hasta que hayamos recuperado 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

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

$resultado->fetch_array(MYSQLI_ASSOC); o $resultado->fetch_assoc(); 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 $resultado->fetch_array(MYSQLI_NUM); o $resultado->fetch_row(); 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, $resultado->fetch_array(MYSQLI_BOTH); nos daría un arreglo con 8 elementos, que tendría ambos arreglos para que nosotros usemos lo que deseamos, este es el comportamiento predeterminado.

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

// Capturar el resultado de la petición en el objeto $resultado
if ($resultado = $mysqli->query( [petición] ) ) {
    // Recuperar las filas en la tabla resultante hasta haber obtenido todas
    while ($fila = $resultado->fetch_array() {
        echo $fila[nombre del campo];
        echo $fila[nombre del campo];
    }
} else {
    die("No se pudo ejecutar la petición" . mysqli_error());
}

Algunos valores útiles de la clase MysqlI

La clase mysqli contiene algunos valores que nos pueden ser útiles:

$mysqli->insert_id

Esto contiene la ID de el último registro que fue insertado.

$mysqli->affected_rows

Esto nos dice cuantas filas fueron afectadas por la última petición que realizamos.

Como usar sentencias preparadas con MysqlI (la clase Mysqli_stmt)

Si nuestra petición a la base de datos contiene información que el usuario del sistema nos provee, o cualquier información sobre la que no tenemos control completo, 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 simple petición de SQL 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, creamos un objetos mysqli_stmt, y entonces ligamos las variables a los espacios marcados. Un ejemplo explicará esto mejor que palabras, primero que nada, creamos un objeto sentencia y preparamos una petición:

$peticion = "insert into `jveweb_net_tabla_prueba` (`campo_nombre`,`campo_correo`) values(?,?)";
$stmt = mysqli->prepare($peticion);

Este método nos devuelve un objeto sentencia que guardamos en $stmt.

Una vez que la sentencia esta preparada, necesitamos ligar las variables usando el método de bind_param() en la clase mysqli_stmt, esto requiere dos o más parámetros, el primero son los tipos de variables en forma de una cadena (s para cadenas, d para números), y a partir de la segunda variable, la lista de variables separadas por comas. Veamos un ejemplo de esto:

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

Y con las variables ligadas, podemos ejecutar la petición con el método execute(). Cuando terminemos con la sentencia preparada la cerramos con el método close() (por supuesto, esto siempre va antes del método close() de la clase mysqli).

$stmt->execute();
$stmt->close();

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($peticion)) {

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

    // Ligamos las variables y ejecutamos la petición
    $stmt->bind_param("ssd", $nombre, $correo, $id);
    $stmt->execute();

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

    // Y ahora cerramos la sentencia
    $stmt->close();
}

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 una sentencia preparada es diferente a la de una petición simple.

Para recuperar la información de una sentencia preparada necesitamos usar los métodos bind_result() y fetch() de la clase mysqli_stmt. 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 fetch() nuestras variables ligadas terminan con la información. Y, al igual que en las peticiones simples, necesitaremos llamar a fetch() hasta haber obtenido todos los datos resultantes si hay más de una fila en el 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->prepare($peticion)) {

    // Ligar las variables y ejecutar la sentencia
    $inicio = 2;
    $fin = 4;
    $stmt->bind_param("dd", $inicio, $fin);
    $stmt->execute();

    // Ligar el resultado y recuperar los datos
    $nombre = "";
    $correo = "";
    $stmt->bind_result($nombre, $correo);
    while ($stmt_fetch()) {
        echo $nombre;
        echo $correo;
    }

    // Y cerramos la sentencia
    $stmt->close();
}

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.

Algunos valores útiles de la clase Mysqli_stmt

Al igual que la clase mysqli, la clase mysqli_stmt contiene dos valores que podrían resultarnos útiles:

$stmt->insert_id

Esto contiene la ID de el último registro que fue insertado.

$stmt->affected_rows

Esto nos dice cuantas filas fueron afectadas por la última petición que realizamos.

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 funciones simples de la librería MysqlI en vez de trabajar con objetos, 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