Comment appliquer la méthode bindValue dans la clause LIMIT?

117

Voici un aperçu de mon code:

$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);

Je reçois

Vous avez une erreur dans votre syntaxe SQL; vérifiez le manuel qui correspond à votre version de serveur MySQL pour la bonne syntaxe à utiliser près de '' 15 ', 15' à la ligne 1

Il semble que PDO ajoute des guillemets simples à mes variables dans la partie LIMIT du code SQL. Je l'ai recherché, j'ai trouvé ce bug qui, je pense, est lié: http://bugs.php.net/bug.php?id=44639

Est-ce ce que je regarde? Ce bug est ouvert depuis avril 2008! Que devons-nous faire en attendant?

J'ai besoin de créer une pagination et de m'assurer que les données sont propres, sécurisées pour l'injection SQL, avant d'envoyer l'instruction SQL.

Nathan H
la source

Réponses:

165

Je me souviens avoir eu ce problème avant. Convertissez la valeur en entier avant de la transmettre à la fonction de liaison. Je pense que cela résout le problème.

$fetchPictures->bindValue(':skip', (int) trim($_GET['skip']), PDO::PARAM_INT);
Stephen Curran
la source
37
Merci! Mais en PHP 5.3, le code ci-dessus a généré une erreur disant "Erreur fatale: impossible de passer le paramètre 2 par référence". Il n'aime pas lancer un int là-bas. Au lieu de (int) trim($_GET['skip']), essayez intval(trim($_GET['skip'])).
Will Martin
5
Ce serait cool si quelqu'un expliquait pourquoi il en est ainsi ... d'un point de vue conception / sécurité (ou autre).
Ross
6
Cela ne fonctionnera que si les instructions préparées émulées sont activées . Il échouera s'il est désactivé (et il devrait être désactivé!)
Madara's Ghost
4
@Ross Je ne peux pas répondre spécifiquement à cela - mais je peux souligner que LIMIT et OFFSET sont des fonctionnalités qui ont été collées APRÈS que toute cette folie PHP / MYSQL / PDO ait frappé le circuit de développement ... En fait, je crois que c'est Lerdorf lui-même qui a supervisé LIMITE la mise en œuvre il y a quelques années. Non, cela ne répond pas à la question, mais cela indique que c'est un add-on de rechange, et vous savez à quel point ils peuvent bien fonctionner parfois ....
FredTheWebGuy
2
@Ross PDO ne permet pas la liaison vers des valeurs - plutôt des variables. Si vous essayez bindParam (': something', 2) vous aurez une erreur car PDO utilise un pointeur vers la variable qu'un nombre ne peut pas avoir (si $ i vaut 2 vous pouvez avoir un pointeur vers $ i mais pas vers le numéro 2).
Kristijan
44

La solution la plus simple serait de désactiver le mode d'émulation. Vous pouvez le faire en ajoutant simplement la ligne suivante

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

De plus, ce mode peut être défini comme paramètre de constructeur lors de la création d'une connexion PDO . Cela pourrait être une meilleure solution car certains rapportent que leur pilote ne prend pas en charge la setAttribute()fonction.

Cela résoudra non seulement votre problème de liaison, mais vous permettra également d'envoyer des valeurs directement dans execute(), ce qui réduira considérablement votre code. En supposant que le mode d'émulation a déjà été défini, toute l'affaire prendra jusqu'à une demi-douzaine de lignes de code

$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);
Votre bon sens
la source
SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes... Pourquoi n'est-ce jamais si simple pour moi :) Bien que je sois sûr que cela attirera la plupart des gens, dans mon cas, j'ai fini par devoir utiliser quelque chose de similaire à la réponse acceptée. Juste un avertissement pour les futurs lecteurs!
Matthew Johnson
@MatthewJohnson de quel pilote s'agit-il?
Votre bon sens
Je ne suis pas sûr, mais dans le manuel, il est dit 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. C'est nouveau pour moi, mais là encore je ne fais que commencer avec l'AOP. J'utilise habituellement mysqli, mais j'ai pensé que j'essaierais d'élargir mes horizons.
Matthew Johnson
@MatthewJohnson si vous utilisez PDO pour mysql, le pilote supporte bien cette fonction. Donc, vous recevez ce message en raison d'une erreur
Votre bon sens
1
Si vous recevez un message de problème de support de pilote, vérifiez à nouveau si vous appelez setAttributel'instruction ($ stm, $ stmt) pas pour l'objet pdo.
Jehong Ahn
17

En regardant le rapport de bogue, les éléments suivants pourraient fonctionner:

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

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

mais êtes-vous sûr que vos données entrantes sont correctes? Parce que dans le message d'erreur, il semble y avoir une seule citation après le nombre (par opposition au nombre entier entre guillemets). Cela peut également être une erreur avec vos données entrantes. Pouvez-vous faire un print_r($_GET);pour le savoir?

Pekka
la source
1
«15», 15 ». Le premier nombre est entièrement entre guillemets. Le deuxième numéro n'a pas du tout de guillemets. Alors oui, les données sont bonnes.
Nathan H
8

C'est juste comme résumé.
Il existe quatre options pour paramétrer les valeurs LIMIT / OFFSET:

  1. Désactivez PDO::ATTR_EMULATE_PREPAREScomme mentionné ci - dessus .

    Ce qui empêche les valeurs transmises par ->execute([...])de toujours apparaître sous forme de chaînes.

  2. Passez au remplissage manuel des ->bindValue(..., ..., PDO::PARAM_INT)paramètres.

    Ce qui est cependant moins pratique qu'une -> liste d'exécution [].

  3. Faites simplement une exception ici et interpolez simplement des entiers simples lors de la préparation de la requête SQL.

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

    Le casting est important. Plus couramment, vous voyez ->prepare(sprintf("SELECT ... LIMIT %d", $num))utilisé à de telles fins.

  4. Si vous n'utilisez pas MySQL, mais par exemple SQLite ou Postgres; vous pouvez également convertir des paramètres liés directement dans SQL.

     SELECT * FROM tbl LIMIT (1 * :limit)

    Encore une fois, MySQL / MariaDB ne prend pas en charge les expressions dans la clause LIMIT. Pas encore.

mario
la source
1
J'aurais utilisé sprintf () avec% d pour 3, je dirais que c'est un peu plus stable qu'avec la variable.
hakre
Oui, la distribution varfunc + interpolation n'est pas l'exemple le plus pratique. J'utiliserais souvent mon paresseux {$_GET->int["limit"]}pour de tels cas.
mario
7

pour LIMIT :init, :end

Vous devez vous lier de cette façon. si vous aviez quelque chose comme $req->execute(Array());ça ne fonctionnera pas car il sera PDO::PARAM_STRconverti en toutes les variables du tableau et pour cela, LIMITvous avez absolument besoin d'un entier. bindValue ou BindParam comme vous le souhaitez.

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

Puisque personne n'a expliqué pourquoi cela se produit, j'ajoute une réponse. La raison pour laquelle il se comporte ainsi est que vous utilisez trim(). Si vous regardez le manuel PHP pour trim, le type de retour est string. Vous essayez alors de transmettre cela comme PDO::PARAM_INT. Voici quelques moyens de contourner ce problème:

  1. Utilisez filter_var($integer, FILTER_VALIDATE_NUMBER_INT)pour vous assurer que vous passez un entier.
  2. Comme d'autres l'ont dit, en utilisant intval()
  3. Casting avec (int)
  4. Vérifier s'il s'agit d'un entier avec is_int()

Il existe de nombreuses autres façons, mais c'est essentiellement la cause principale.

Melissa Williams
la source
3
Cela se produit même lorsque la variable a toujours été un entier.
felwithe le
1

bindValue offset et limit en utilisant PDO :: PARAM_INT et cela fonctionnera

Karel
la source
-1

// AVANT (Erreur présente) $ query = ".... LIMIT: p1, 30;"; ... $ stmt-> bindParam (': p1', $ limiteInferior);

// APRÈS (Erreur corrigée) $ query = ".... LIMIT: p1, 30;"; ... $ limiteInferior = (int) $ limiteInferior; $ stmt-> bindParam (': p1', $ limiteInferior, PDO :: PARAM_INT);

Brayan Josue Medina Melendez
la source
-1

PDO::ATTR_EMULATE_PREPARES m'a donné le

Le pilote ne prend pas en charge cette fonction: ce pilote ne prend pas en charge l'erreur de définition des attributs.

Ma solution de contournement était de définir une $limitvariable sous forme de chaîne, puis de la combiner dans l'instruction prepare comme dans l'exemple suivant:

$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 ) {
    ...
}
Palmes
la source
-1

Il se passe beaucoup de choses entre les différentes versions de PHP et les bizarreries de PDO. J'ai essayé 3 ou 4 méthodes ici mais je n'ai pas pu faire fonctionner LIMIT.
Ma suggestion est d'utiliser le formatage / concatination de chaîne AVEC un filtre intval () :

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

Il est très important d'utiliser intval () pour empêcher l'injection SQL, en particulier si vous obtenez votre limite de $ _GET ou autre. Si vous faites cela, c'est le moyen le plus simple de faire fonctionner LIMIT.

On parle beaucoup du «problème avec LIMIT dans PDO» mais ma pensée ici est que les paramètres PDO n'ont jamais été utilisés pour LIMIT car ils seront toujours des entiers et un filtre rapide fonctionne. Pourtant, c'est un peu trompeur car la philosophie a toujours été de ne pas faire de filtrage d'injection SQL soi-même, mais plutôt de «faire en sorte que PDO le gère».

Tycon
la source