¿Cómo aplicar el método bindValue en la cláusula LIMIT?

117

Aquí hay una instantánea de mi código:

$fetchPictures = $PDO->prepare("SELECT * 
    FROM pictures 
    WHERE album = :albumId 
    ORDER BY id ASC 
    LIMIT :skip, :max");

$fetchPictures->bindValue(':albumId', $_GET['albumid'], PDO::PARAM_INT);

if(isset($_GET['skip'])) {
    $fetchPictures->bindValue(':skip', trim($_GET['skip']), PDO::PARAM_INT);    
} else {
    $fetchPictures->bindValue(':skip', 0, PDO::PARAM_INT);  
}

$fetchPictures->bindValue(':max', $max, PDO::PARAM_INT);
$fetchPictures->execute() or die(print_r($fetchPictures->errorInfo()));
$pictures = $fetchPictures->fetchAll(PDO::FETCH_ASSOC);

yo obtengo

Tiene un error en su sintaxis SQL; Consulte el manual que corresponde a la versión de su servidor MySQL para conocer la sintaxis correcta para usar cerca de '' 15 ', 15' en la línea 1

Parece que PDO agrega comillas simples a mis variables en la parte LIMIT del código SQL. Lo busqué y encontré este error que creo que está relacionado: http://bugs.php.net/bug.php?id=44639

¿Es eso lo que estoy mirando? ¡Este error se ha abierto desde abril de 2008! ¿Qué se supone que debemos hacer mientras tanto?

Necesito crear algo de paginación, y necesito asegurarme de que los datos estén limpios, seguros para la inyección de sql, antes de enviar la declaración sql.

Nathan H
fuente

Respuestas:

165

Recuerdo haber tenido este problema antes. Convierta el valor en un número entero antes de pasarlo a la función de enlace. Creo que esto lo resuelve.

$fetchPictures->bindValue(':skip', (int) trim($_GET['skip']), PDO::PARAM_INT);
Stephen Curran
fuente
37
¡Gracias! Pero en PHP 5.3, el código anterior arrojó un error que decía "Error fatal: No se puede pasar el parámetro 2 por referencia". No le gusta lanzar un int allí. En lugar de (int) trim($_GET['skip'])intentarlo intval(trim($_GET['skip'])).
Will Martin
5
Sería genial si alguien explicara por qué esto es así ... desde un punto de vista de diseño / seguridad (u otro).
Ross
6
Esto solo funcionará si las declaraciones preparadas emuladas están habilitadas . Fallará si está desactivado (¡y debería estar desactivado!)
Madara's Ghost
4
@Ross No puedo responder específicamente a esto, pero puedo señalar que LIMIT y OFFSET son características que se pegaron DESPUÉS de que toda esta locura de PHP / MYSQL / PDO golpeó el circuito de desarrollo ... De hecho, creo que fue el mismo Lerdorf quien supervisó LIMIT la implementación hace unos años. No, no responde a la pregunta, pero sí indica que es un complemento del mercado de accesorios, y sabes lo bien que pueden funcionar a veces ...
FredTheWebGuy
2
@Ross PDO no permite la vinculación a valores, sino a variables. Si intenta bindParam (': algo', 2) tendrá un error ya que PDO usa un puntero a la variable que un número no puede tener (si $ i es 2, puede tener un puntero hacia $ i pero no hacia el Número 2).
Kristijan
44

La solución más sencilla sería desactivar el modo de emulación. Puedes hacerlo simplemente agregando la siguiente línea

$PDO->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );

Además, este modo se puede establecer como un parámetro de constructor al crear una conexión PDO . Podría ser una mejor solución ya que algunos informan que su controlador no es compatible con la setAttribute()función.

No solo resolverá su problema con el enlace, sino que también le permitirá enviar valores directamente execute(), lo que hará que su código sea mucho más corto. Suponiendo que el modo de emulación ya se haya configurado, todo el asunto tomará hasta media docena de líneas de código.

$skip = isset($_GET['skip']) ? (int)trim($_GET['skip']) : 0;
$sql  = "SELECT * FROM pictures WHERE album = ? ORDER BY id LIMIT ?, ?";
$stmt  = $PDO->prepare($sql);
$stmt->execute([$_GET['albumid'], $skip, $max]);
$pictures = $stmt->fetchAll(PDO::FETCH_ASSOC);
Tu sentido común
fuente
SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes... ¿Por qué nunca es tan simple para :) Si bien estoy seguro de que esto hará que la mayoría de la gente esté allí, en mi caso terminé teniendo que usar algo similar a la respuesta aceptada. ¡Solo un aviso para futuros lectores!
Matthew Johnson
@MatthewJohnson ¿qué conductor es?
Tu sentido común
No estoy seguro, pero en el manual dice PDO::ATTR_EMULATE_PREPARES Enables or disables emulation of prepared statements. Some drivers do not support native prepared statements or have limited support for them. Es nuevo para mí, pero nuevamente estoy comenzando con PDO. Por lo general, uso mysqli, pero pensé que intentaría ampliar mis horizontes.
Matthew Johnson
@MatthewJohnson si está utilizando PDO para mysql, el controlador es compatible con esta función. Entonces, está recibiendo este mensaje debido a un error
su sentido común
1
Si recibió un mensaje de problema de soporte del controlador, verifique nuevamente si solicita setAttributela declaración ($ stm, $ stmt) y no el objeto pdo.
Jehong Ahn
17

Mirando el informe de errores, lo siguiente podría funcionar:

$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);

$fetchPictures->bindValue(':skip', (int)trim($_GET['skip']), PDO::PARAM_INT);  

pero ¿está seguro de que sus datos entrantes son correctos? Porque en el mensaje de error, parece haber solo una comilla después del número (en lugar de que el número entero esté entre comillas). Esto también podría ser un error con sus datos entrantes. ¿Puedes hacer algo print_r($_GET);para averiguarlo?

Pekka
fuente
1
'' 15 ', 15'. El primer número está entre comillas. El segundo número no tiene comillas. Entonces sí, los datos son buenos.
Nathan H
8

Esto es solo un resumen.
Hay cuatro opciones para parametrizar valores LIMIT / OFFSET:

  1. Desactivar PDO::ATTR_EMULATE_PREPAREScomo se mencionó anteriormente .

    Lo que evita que los valores pasados ​​por ->execute([...])se muestren siempre como cadenas.

  2. Cambiar a ->bindValue(..., ..., PDO::PARAM_INT)población de parámetros manual .

    Sin embargo, es menos conveniente que una -> lista de ejecución [].

  3. Simplemente haga una excepción aquí e interpole enteros sin formato cuando prepare la consulta SQL.

     $limit = intval($limit);
     $s = $pdo->prepare("SELECT * FROM tbl LIMIT {$limit}");

    El casting es importante. Más comúnmente se ve ->prepare(sprintf("SELECT ... LIMIT %d", $num))utilizado para tales fines.

  4. Si no está utilizando MySQL, pero, por ejemplo, SQLite o Postgres; también puede convertir parámetros enlazados directamente en SQL.

     SELECT * FROM tbl LIMIT (1 * :limit)

    Nuevamente, MySQL / MariaDB no admite expresiones en la cláusula LIMIT. Aún no.

3 revoluciones
fuente
1
Habría usado sprintf () con% d para 3, diría que es un poco más estable que con la variable.
hakre
Sí, la interpolación varfunc cast + no es el ejemplo más práctico. A menudo uso mi perezoso {$_GET->int["limit"]}para tales casos.
mario
7

para LIMIT :init, :end

Necesitas unirte de esa manera. si tiene algo así $req->execute(Array());, no funcionará, ya que se enviará PDO::PARAM_STRa todas las variables de la matriz y, para el, LIMITes absolutamente necesario un número entero. bindValue o BindParam como desee.

$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);
Nicolas Manzini
fuente
2

Como nadie ha explicado por qué sucede esto, agrego una respuesta. La razón por la que se está comportando así es porque está usando trim(). Si consulta el manual de PHP trim, el tipo de retorno es string. Entonces estás intentando pasar esto como PDO::PARAM_INT. Algunas formas de evitar esto son:

  1. Utilizar filter_var($integer, FILTER_VALIDATE_NUMBER_INT) para asegurarse de que está pasando un número entero.
  2. Como dijeron otros, usando intval()
  3. Casting con (int)
  4. Comprobando si es un entero con is_int()

Hay muchas más formas, pero esta es básicamente la causa principal.

Melissa Williams
fuente
3
Ocurre incluso cuando la variable siempre ha sido un número entero.
felwithe
1

bindValue offset y limit usando PDO :: PARAM_INT y funcionará

Karel
fuente
-1

// ANTES (Error presente) $ consulta = ".... LÍMITE: p1, 30;"; ... $ stmt-> bindParam (': p1', $ limiteInferior);

// DESPUÉS (Error corregido) $ consulta = ".... LÍMITE: p1, 30;"; ... $ limiteInferior = (int) $ limiteInferior; $ stmt-> bindParam (': p1', $ limiteInferior, PDO :: PARAM_INT);

Brayan Josue Medina Meléndez
fuente
-1

PDO::ATTR_EMULATE_PREPARES me dio el

El controlador no admite esta función: este controlador no admite el error de configuración de atributos.

Mi solución fue establecer una $limitvariable como una cadena, luego combinarla en la declaración de preparación como en el siguiente ejemplo:

$limit = ' LIMIT ' . $from . ', ' . $max_results;
$stmt = $pdo->prepare( 'SELECT * FROM users WHERE company_id = :cid ORDER BY name ASC' . $limit . ';' );
try {
    $stmt->execute( array( ':cid' => $company_id ) );
    ...
}
catch ( Exception $e ) {
    ...
}
Aletas
fuente
-1

Está sucediendo mucho entre las diferentes versiones de PHP y las rarezas de PDO. Probé 3 o 4 métodos aquí pero no pude hacer que LIMIT funcionara.
Mi sugerencia es usar formato de cadena / concatenación CON un filtro intval () :

$sql = 'SELECT * FROM `table` LIMIT ' . intval($limitstart) . ' , ' . intval($num).';';

Es muy importante usar intval () para evitar la inyección de SQL, particularmente si obtiene su límite de $ _GET o similar. Si lo hace, esta es la forma más fácil de hacer que LIMIT funcione.

Se habla mucho sobre 'El problema con LIMIT en PDO', pero mi pensamiento aquí es que los parámetros de PDO nunca se usaron para LIMIT, ya que siempre serán números enteros y un filtro rápido funciona. Aún así, es un poco engañoso, ya que la filosofía siempre ha sido no realizar ningún filtrado de inyección SQL usted mismo, sino más bien 'Hacer que PDO lo maneje'.

Tycon
fuente