«¿Puedo vincular un array a una condición IN()?» — la pregunta que atormenta a uno de cada dos desarrolladores PHP
\n\n¿Alguna vez ha escrito una consulta como SELECT * FROM users WHERE id IN (?) e intentó pasarle un array mediante bindValue()? Si es así, conoce exactamente esa sensación de decepción cuando PDO devuelve solo una fila en lugar de todas las necesarias. O peor aún, lanza un error.
Esta pregunta en Stack Overflow ha acumulado miles de visitas, y no es casualidad. Parecería simple: pasar un array, obtener un resultado. Pero PDO (PHP Data Objects) es algo estricto. No sabe cómo «desplegar» arrays en una lista de valores para IN() automáticamente. Y aquí es donde comienza lo interesante: los desarrolladores cometen los mejores errores que analizaremos hoy. Y lo más importante: aprenderemos a evitarlos.
En este artículo no solo daremos una solución lista. Recorreremos el camino desde el código incorrecto hasta uno seguro, eficiente y elegante. ¡Allá vamos!
\n\nError n.º 1: Pasar ingenuamente un array mediante bindValue
\n\nEl «rastrillo» más común: intentar alimentar un array directamente a bindValue().
// ❌ INCORRECTO\n$ids = [1, 2, 3, 5];\n$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN (?)");\n$stmt->bindValue(1, $ids, PDO::PARAM_STR);\n$stmt->execute();\n$users = $stmt->fetchAll();\n\n¿Qué sucederá? PHP convertirá el array en la cadena "Array", y la consulta se convertirá en WHERE id IN ("Array"). El resultado: cero filas o un error, dependiendo del controlador. PDO simplemente no entiende que el array debe descomponerse en marcadores de posición individuales.
¿Por qué sucede esto? PDO espera un valor escalar (cadena, número, null). Un array es un tipo compuesto y no es adecuado para la vinculación directa.
\n\n¿Cómo solucionarlo?
\nEs necesario crear manualmente marcadores de posición para cada elemento del array. Sin magia, solo código.
\n\n// ✅ CORRECTO\n$ids = [1, 2, 3, 5];\n$placeholders = implode(',', array_fill(0, count($ids), '?'));\n$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");\n$stmt->execute($ids);\n$users = $stmt->fetchAll();\n\nAquí creamos una cadena como ?,?,?,? y pasamos el array directamente a execute(). PDO sustituirá los valores en orden. Simple y seguro.
Error n.º 2: Ignorar los tipos de datos
\n\nSupongamos que usa marcadores de posición con nombre. Y surge la tentación de hacer esto:
\n\n// ❌ INCORRECTO\n$ids = [1, 2, 3];\n$placeholders = [];\nforeach ($ids as $i => $id) {\n $placeholders[] = ":id_$i";\n}\n$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $placeholders) . ")";\n$stmt = $pdo->prepare($sql);\nforeach ($ids as $i => $id) {\n $stmt->bindValue(":id_$i", $id, PDO::PARAM_INT);\n}\n$stmt->execute();\n\nParece correcto. Pero hay un detalle: si el array está vacío, la consulta se convierte en WHERE id IN (), lo que provocará un error de sintaxis SQL. El segundo problema: si el array contiene cadenas y usted especificó PDO::PARAM_INT, PDO podría convertirlas a cero.
Solución: siempre verifique si el array está vacío y determine el tipo dinámicamente.
\n\n// ✅ CORRECTO\n$ids = [1, 2, 3];\nif (empty($ids)) {\n return []; // o lanzar una excepción\n}\n$placeholders = [];\nforeach ($ids as $i => $id) {\n $placeholders[] = ":id_$i";\n}\n$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $placeholders) . ")";\n$stmt = $pdo->prepare($sql);\nforeach ($ids as $i => $id) {\n $type = is_int($id) ? PDO::PARAM_INT : PDO::PARAM_STR;\n $stmt->bindValue(":id_$i", $id, $type);\n}\n$stmt->execute();\n\nError n.º 3: Inyección SQL mediante «escape» manual
\n\nAlgunos desarrolladores, desesperados, escriben algo como