php-sentencias-preparadas-con-pdo-en-php

Sentencias preparadas con PDO en PHP

  • 3 min

Una sentencia preparada es una consulta SQL cuya estructura se envía separada de sus valores.

La “Inyección SQL” ocurre cuando un hacker engaña a tu base de datos para que ejecute comandos que tú no escribiste.

El error clásico (Concatenación):

// 🛑 PELIGRO DE MUERTE
$sql = "SELECT * FROM usuarios WHERE email = '" . $_POST['email'] . "'";
Copied!

Si el usuario escribe en el campo email: ' OR '1'='1, la consulta final se convierte en SELECT * FROM usuarios WHERE email = '' OR '1'='1'. Como 1 siempre es igual a 1, la base de datos devuelve todos los usuarios, permitiendo al hacker entrar como administrador sin contraseña.

¿Cómo lo solucionan las sentencias preparadas?

La lógica cambia radicalmente. En lugar de enviar la consulta y los datos mezclados en una sola frase, lo hacemos en dos pasos separados:

  1. Preparar (Prepare): Enviamos la estructura SQL a la base de datos sin datos. Le decimos: “Oye, voy a querer ejecutar esto, pero dejo unos huecos vacíos (marcadores) para rellenar luego”. La base de datos compila y optimiza esa orden.
  2. Vincular y ejecutar (bind y execute): Enviamos los valores por separado. El driver los trata como parámetros y no como parte de la sintaxis SQL, respetando además el tipo indicado cuando lo vinculamos explícitamente.

Los marcadores (placeholders)

En lugar de poner la variable $email, ponemos un marcador. En PDO hay dos tipos, pero recomendamos encarecidamente los Marcadores con Nombre (empiezan por dos puntos :) porque son más fáciles de leer.

// Definimos la plantilla con marcadores (:email y :estado)
$sql = "SELECT * FROM usuarios WHERE email = :email AND activo = :estado";

// Preparamos la sentencia
$stmt = $pdo->prepare($sql);
Copied!

Ejecución con array (la forma moderna)

La forma más limpia de pasar los datos es entregando un array asociativo directamente al método execute(). Las claves del array deben coincidir con los marcadores.

$emailUsuario = $_POST['email']; // Dato sucio/peligroso del usuario

// Ejecutamos pasando el dato como parámetro; aún debemos validarlo
// según las reglas de negocio de la aplicación.
$stmt->execute([
    'email'  => $emailUsuario,
    'estado' => 1
]);

$resultado = $stmt->fetch();
Copied!

Métodos bindValue y bindParam (nivel detallado)

A veces necesitas más control (por ejemplo, especificar que un dato es un número entero INT y no un texto, para límites en LIMIT). Para eso vinculamos los parámetros antes de ejecutar.

  • bindValue(':id', $id): Asigna el valor en ese momento exacto. (El más usado).
  • bindParam(':id', $id): Vincula la variable. Si cambias la variable $id después del bind pero antes del execute, el valor cambia.
$id = 5;
$limite = 10;

$stmt = $pdo->prepare("SELECT * FROM productos WHERE categoria_id = :id LIMIT :limite");

// Para LIMIT, MySQL a veces exige que el dato sea explícitamente un entero
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->bindValue(':limite', $limite, PDO::PARAM_INT);

$stmt->execute();
Copied!