¿Por qué se sigue usando mysql_query?

digital-388075_1920

¿Por qué se sigue usando todavía mysql_query? ¿Hay programadores que aún no utilizan PDO?

Últimamente me están llegando muchas consultas de programadores que se están iniciando en este apasionante mundo y de muchos otros que ya llevan tiempo programando, pero que continúan haciéndolo como les enseñaron y veo que se sigue utilizando mucho las funciones de consulta a las bases de datos de mysql_query y compañía.

Ciertamente, no entiendo el motivo por el que todavía se enseña a programar así, de hecho, no entiendo por qué desde el primer momento no se enseña a programar correctamente. Hay una gran tendencia en los cursos y masters a separar la parte de PHP del MySQL, el HTML y el CSS. Eso sin meterme en javascript, que quizás es lo único que puedo entender que se separe del resto. Hoy en día para programar una página web necesitas de estos 5 elementos y cuando aprendes a programar webs, nunca sabes si vas a trabajar en un equipo con analistas expertos que te van a indicar cómo ensamblar estas partes y te lo van a dar todo hecho, o si vas a estar sólo y tendrás que encargarte de realizar todo tu. Si una de estas partes cojea, no te vas a divertir programando y el resultado puede ser desastroso. Porque lo que está claro es que si no te diviertes programando te has equivocado de camino. Es fundamental disfrutarlo para poder pasar tantas horas delante de una pantalla y un teclado.

Lo importante de este artículo es que comprendas que tienes que dejar ya de conectarte a la base de datos utilizando mysql_connect y todos sus derivados. Estas funciones ahora mismo ya están obsoletas, pero siempre han sido muy inseguras y nada recomendables.

También es válido para aquellos que hayan heredado el código anticuado de una web programada por otro y tengan la mala suerte de que todas las consultas están obsoletas. Lo primero es ponerse manos a la obra y modernizar el código

Aunque utilices funciones y validaciones para proteger los datos que introduces en la query, con este código anticuado es muy sencillo que te hagan una inyección de código SQL, que para el que no lo sepa, no es otra cosa que modificar la Query que has creado para realizar una consulta a la base de datos y ejecutar otra cosa que dependerá de lo que quiera hacer el atacante. La modificación de esa query te la pueden hacer, por ejemplo, colándose en tu código a través de las variables de entrada de un formulario, o modificando tu URL.

Como ejemplo, si tienes esta inocente consulta:

SELECT ciudad FROM ciudades WHERE país = '.$id_pais.'

El punto débil de esta consulta es la variable $id_pais. Es una consulta muy peligrosa si esta variable viene de un formulario, o la recogemos de la URL asi (no hacerlo nunca):

$id_pais = $_GET['id_pais'];

Con una consulta como esta en nuestro código nuestra base de datos tiene los dias contados. Por esa variable se puede “ampliar” tu query añadiendo código malicioso, y un usuario malintencionado puede leer tus tablas, borrarlas, alterar los campos. Al final te quedarás sin tablas, es sólo cuestión de tiempo.

Esto ha pasado siempre y lo lamentable es que en muchos cursos te enseñan a programar así, cuando estás empezando puedes pensar que está bien así, porque funciona, y si no te preocupas de profundizar un poco en el tema de la seguridad lo pones en producción así en tu web, y a los 3 días te quedas sin ella.

La solución es utilizar una clase que viene incluida desde la versión 5.1 de PHP y se llama PDO combinando con una “query o sentencia preparada”, ahora explico para los mas nuevos que significan todos estos conceptos y que diferencia hay en cuanto al funcionamiento entre las funciones ya obsoletas y la utilización de sentencias preparadas. Siempre hay que tener claro, que estas medidas tienen que ir apoyadas por una buena validación de los datos a nivel de servidor (con PHP, no sirve con javascript), ya que en programación web no existe la seguridad total, así que mientras más seguridad metamos, mejor.

Empezamos explicando qué significan estos conceptos. Si estás habituado a programar con objetos puedes pasar al resumen al final del artículo, donde está el código necesario para hacer funcionar esta clase.

Conceptos básicos

Clase (Programacion orientada a objetos POO):

Si no sabes lo que es una clase, te va a costar un poco seguir manteniendo el hilo, pero mi objetivo es que no sigas utilizando las funciones obsoletas, asi que intenta seguir leyendo y cuando acabes date una vuelta por la web del maestro Cesar Cancino y aprende a programar usando objetos urgentemente. Es el mejor sitio para aprender esta forma de programar desde cero.

La programación utilizando objetos es una forma de programar, por una parte tenemos la forma clásica de programar que es ir tirando código, y cuando queramos modificar algo, buscar dónde insertarlo repetir una y otra vez trozos de código. Como decía un profesor que tuve en un curso, programar así es como si construyes algo con plastilina, cuando intentas modificarlo y añadir algo entre medias se complica y llega un momento que la plastilina se mezcla de tal forma que es imposible modificar nada. Para evitar esto se creó la programación estructurada, donde se introducen las funciones. Con el uso de funciones se consigue fundamentalmente reutilizar el código y programar de forma mas limpia y estructurada.

La programación orientada a objetos es un tipo de programación estructurada, pero las “funciones” que se utilizan tienen una estructura y unas características particulares y para crearlas y utilizarlas hay que seguir una serie de pasos.

Para que se comprenda bien, cuando creas el código que vas a querer reutilizar siguiendo estos pasos para obtener tu “función especial” se dice que estas creando una clase.

Cuando has creado esta Clase y necesites utilizarla, tendrás que “llamarla” de forma especial para ejecutarla. Cuando utilizas los pasos necesarios para “llamar” a esta clase obtienes lo que se llama un Objeto de esta clase. En términos de POO se dice que un objeto es una instancia de una clase. Cada vez que instancias una clase obtienes un objeto independiente.

Como introducción, es suficiente para poder seguir con el objetivo de este post, tampoco quiero aburrir demasiado a los lectores que conocen perfectamente la POO. No olvides darte un paseo por la web de Cesar Cancino si no conoces la POO.

PDO:

Es una clase de PHP que nos proporciona la conexión con la base de datos y que nos da mas seguridad, velocidad y estabilidad a la hora de realizar nuestras consultas.

Permite la utilización de sentencias preparadas PDOStatement

Query o Sentencia preparada (Prepared statement):

Una sentencia preparada es un método de escribir tu query que le indica a la clase PDO que queremos utilizar una seguridad adicional a nuestras consultas con la base de datos para evitar la inyección de código. La forma de escribir las querys es muy sencilla y la veremos mas adelante con mas profundidad.

Hay dos métodos, pero voy a explicar el que uso yo, que es sustituir las variables por signos de interrogación. Nuestra query anterior se quedaría asi:

SELECT ciudad FROM ciudades WHERE país = ?

Diferencia entre mysql_query y PDOStatement

De forma sencilla, la diferencia principal está en la forma de conectarse, es decir, mysql_query envía a la base de datos a la vez la query y las variables, por lo que si existe código malicioso se compila todo y se aplica directamente. Al utilizar la sentencia preparada, PDO envía primero la query y se compila. Ahora está listo para recibir las variables en las posiciones que teníamos las interrogaciones, por tanto si luego se recibe código malicioso por las variables, que es por donde un usuario malintencionado enviaría su código, no se va a ejecutar, porque la query ya ha sido compilada anteriormente y convertida al código propio de SQL, mientras que el código inyectado que vino en segundo lugar no va a ser compilado, sino tratado como una variable.  Una vez enviamos las variables, se realiza además automáticamente un “escapado” del contenido, lo que nos da una protección adicional quedando el posible código malicioso inutilizable. De todas formas tienes que tener claro que esto no es infalible, y que debes validar siempre los datos, aunque eso suponga meter más código y aumentar los tiempos de ejecución.

Conexión a la Base de datos.

En primer lugar debemos iniciar la conexión con nuestra base de datos, para poder empezar a enviar las consultas a la conexión que tenemos abierta. A esta conexión le damos un nombre siguiendo las reglas para las variables, yo le voy a llamar $conexion

Esta variable, una vez iniciada la conexión, será un objeto de la clase PDO. En lenguaje de programación orientada a objetos se dice que hemos instanciado la clase PDO. Se utiliza la expresión new, y hay que pasar una serie de parámetros que dependerán de las propiedades que queremos que tenga nuestra conexión.

$conexion = new PDO("mysql:host=servidor;dbname=nombrebasedatos",usuario,clave,$arrayConfg);

mysql:host : Si estamos usando una base de datos MySql se lo indicamos de este modo a la clase.

servidor: Debemos cambiarlo por la Ip de nuestro servidor, o bien localhost

nombrebasedatos: Lo sustituimos por el nombre de nuestra base de datos a la que queremos conectarnos

usuario y clave: Son el nombre de usuario y la contraseña que necesitamos para conectarnos a la base de datos. Recuerda que puedes tener usuarios con permisos restringidos para solo lectura, lo que da mayor seguridad a tus aplicaciones. Para las partes de tu web donde necesitas insertar o modificar registros puedes iniciar la conexión con un usuario de nivel superior

$arrayConf: Pasamos un array con las configuraciones adicionales, muy recomendable, por las razones que puedes ver en este articulo, indicar el Cotejamiento que necesitamos.

Yo siempre utilizo estos dos parámetros como minimo:

$arrayConf = array(PDO::ATTR_PERSISTENT => FALSE, PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES \'UTF8\'" );

En la web de php podrás encontrar un listado completo de parámetros.

Ejemplo de conexión, usando el array de configuración anterior:

$conexion = new PDO("mysql:host=localhost;dbname=miblog","miblogadmin" ,"mipassword",$arrayConf);

Ya tenemos el objeto $conexion que utilizaremos para realizar las operaciones con la base de datos, pero antes vamos a activar unos atributos de la conexión para podamos comprobar los posibles errores durante el desarrollo

Para los iniciados en la programación orientada a objetos, una vez creado el objeto $conexion, cualquier operación que queramos hacer sobre este objeto, la vamos a realizar utilizando una flecha creada con un guión y el símbolo menor: ->

$conexion->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_WARNING);

Este setAttribute es una función. Cuando estamos trabajando con objetos se llama método y se dice que setAttribute es un método de la clase PDO. Ya todo lo que hagamos con los objetos será a través de los métodos (funciones). Como cualquier otra función, entre paréntesis le pasamos los argumentos que necesitemos. La función de la flecha es la de realizar la llamada a la función (método).

Ya tenemos abierta la conexión, ahora podemos empezar a ejecutar nuestras consultas.

Preparacion de la query:

$query = "SELECT `ciudades`.`ciudad` FROM `ciudades` WHERE `ciudades`.`país` = ? AND `ciudades`.`region` = ?";
$pdolink = $conexion->prepare($query);

En este punto ya tenemos la consulta compilada y está esperando que le pasemos los parámetros. Los parámetros se trataran como parámetros y no provocarán modificación alguna de nuestra consulta original.

Por cada ? que tenemos en nuestra query tenemos que ejecutar una vez bindValue, hay que hacerlo en el mismo orden que nos vamos encontrando las variables al leer la query.

En nuestro ejemplo tenemos 2 variables, país y región.

$pdolink ->bindValue( 1, $pais);
$pdolink ->bindValue( 2, $region);

La primera variable indica la posición de la ? en la query y la segunda la variable que sustituye a esa ?. Yo siempre lo hago en el orden de lectura de la consulta.

Ahora ejecutamos la consulta y extraemos los resultados.

$pdolink ->execute();

Para extraer los resultados, podemos usar fetch o fetchAll. La diferencia es que fetchAll te devuelve la consulta completa en un array que podemos tratar con un foreach, mientras que fetch hay que recorrerlo con un while, y devuelve una sola fila cada vez. Yo siempre encuentro mas útil tener toda la consulta en un array, además es más rápido.

$resultado = $pdolink->fetchAll(PDO::FETCH_BOTH);

El parámetro PDO::FETCH_BOTH indica que nos devuelva un array tanto numérico como asociativo. Si queremos solo un array numérico usaremos PDO::FETCH_NUM. Si queremos sólo un array asociativo pasaremos PDO::FETCH_ASSOC

Dependiendo de cada caso suelo usar PDO::FETCH_ASSOC o PDO::FETCH_NUM. Nunca utilizo PDO::FETCH_BOTH

Resumiendo, así quedaría nuestro código para seleccionar las ciudades de una región y país determinado.

$conexion = new PDO("mysql:host=localhost;dbname=miblog","miblogadmin" ,"mipassword",$arrayConf);
$conexion->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_WARNING);
$query = "SELECT `ciudades`.`ciudad` FROM `ciudades` WHERE `ciudades`.`país` = ? AND `ciudades`.`region` = ?";
$pdolink              = $conexion->prepare($query);
$pdolink ->bindValue( 1, $pais);
$pdolink ->bindValue( 2, $region);
$pdolink ->execute();
$resultado = $pdolink->fetchAll(PDO::FETCH_ ASSOC);

Las sentencias INSERT y UPDATE se realizan de igual forma, pero acaban en $pdolink->execute(); ya que no devuelve ningún resultado y no se aplica el fetchAll.

IMPORTANTE: Tener en cuenta que si se espera una sóla línea, el resultado sigue siendo igualmente un array. Es muy común equivocarse.

Ejemplo:

$query = "SELECT `ciudades`.`ciudad` FROM `ciudades` WHERE `ciudades`.`id_ciudad` = ?";


Nos devolvería la ciudad con el ID único que queramos.

Es común confundirse y hacer

echo $resultado['ciudad'];

Cuando lo que debemos hacer es:

echo $resultado[0]['ciudad'];

Porque el resultado que nos devuelve fetchAll es siempre un array. Para estos casos quizás es mejor utilizar fetch, aunque yo no lo hago.

3 comentarios en “¿Por qué se sigue usando mysql_query?

  1. Jhen

    ¡Buenas noches Dani!

    Interesante blog, me está sirviendo de mucho. Justamente estos días estoy aprendiendo a hacer este tipo de consultas y lo hacía como tú dices, pero me informé sobre el tema de la inyección SQL y encontré otra solución sin utilizar POO, la función mysqli_real_escape_string(). Ahora que veo esto me planteo su seguridad. ¿La has usado alguna vez? De ser así, ¿recomiendas su uso?

    ¡Muchas gracias!

    Responder
    1. dbarreno

      Hola Jhen,
      La he usado hace tiempo en aplicaciones con versiones anterior a la 5.0, al principio la usaba combinada con validaciones con expresiones regulares, ya que lo único que hace esa función es escapar algunos caracteres especiales pero no es tan eficaz como una sentencia preparada (PDO Prepared Statement). No hay ningún problema en usar POO si programas de forma lineal o con funciones (Sin Clases), a la hora de realizar la conexión con la base de datos puedes crear el objeto PDO y utilizarlo igualemente.
      La diferencia fundamental es que la sentencia preparada envía y compila la query SQL, y se queda esperando a los parámetros y sabiendo que lo que va a recibir luego mediante bindValue no es código SQL, con lo cual dificulta cierto tipo de ataques. Recuerda que la seguridad total no existe. Yo sigo utilizando siempre expresiones regulares y funciones del tipo is_numeric() para validar los datos antes de enviarlos a la base de datos.
      Por cierto, para que funcione correctamente mysqli_real_escape_string tiene que estar bien configurado el cotejamiento de caracteres en la conexión, por ejemplo, si estás trabajando en UTF-8 hay que enviar mysqli_set_charset('utf8') justo despues de abrir la conexión.
      Gracias por el comentario.
      Saludos

      Responder
  2. Julian

    Muy interesantes tus artículos, voy a mejorar mis métodos en base a lo que comentas.

    Tengo una inquietud, que diferencia existe en

    SELECT ciudad FROM ciudades WHERE país = '.$id_pais.'

    y

    $pdolink ->bindValue( 1, $pais);
    $pdolink ->bindValue( 2, $region);

    si por ejemplo, si a las variables $pais y $region se recibieran igual que $id_pais a través de un POST o GET o Variables de sesion. Como se evitaría entonces la inyección de código.

    Un saludo.

    Responder

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *