Wie wende ich die bindValue-Methode in der LIMIT-Klausel an?

117

Hier ist eine Momentaufnahme meines Codes:

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

Ich bekomme

Sie haben einen Fehler in Ihrer SQL-Syntax. Überprüfen Sie das Handbuch, das Ihrer MySQL-Serverversion entspricht, auf die richtige Syntax für die Verwendung in der Nähe von '' 15 ', 15' in Zeile 1

Es scheint, dass PDO meinen Variablen im LIMIT-Teil des SQL-Codes einfache Anführungszeichen hinzufügt. Ich habe nachgeschlagen und diesen Fehler gefunden, von dem ich denke, dass er damit zusammenhängt: http://bugs.php.net/bug.php?id=44639

Ist es das, was ich sehe? Dieser Bug wurde seit April 2008 geöffnet! Was sollen wir in der Zwischenzeit tun?

Ich muss eine Paginierung erstellen und sicherstellen, dass die Daten sauber und SQL-Injection-sicher sind, bevor ich die SQL-Anweisung sende.

Nathan H.
quelle

Antworten:

165

Ich erinnere mich, dass ich dieses Problem schon einmal hatte. Wandeln Sie den Wert in eine Ganzzahl um, bevor Sie ihn an die Bindefunktion übergeben. Ich denke das löst es.

$fetchPictures->bindValue(':skip', (int) trim($_GET['skip']), PDO::PARAM_INT);
Stephen Curran
quelle
37
Vielen Dank! In PHP 5.3 gab der obige Code jedoch den Fehler "Schwerwiegender Fehler: Parameter 2 kann nicht als Referenz übergeben werden" aus. Es mag es nicht, dort ein Int zu werfen. (int) trim($_GET['skip'])Versuchen Sie es stattdessen intval(trim($_GET['skip'])).
Will Martin
5
wäre cool, wenn jemand die Erklärung liefern würde, warum dies so ist ... vom Standpunkt des Designs / der Sicherheit (oder eines anderen).
Ross
6
Dies funktioniert nur, wenn emulierte vorbereitete Anweisungen aktiviert sind . Es wird fehlschlagen, wenn es deaktiviert ist (und es sollte deaktiviert sein!)
Madara's Ghost
4
@ Ross Ich kann das nicht konkret beantworten - aber ich kann darauf hinweisen, dass LIMIT und OFFSET Funktionen sind, die aufgeklebt wurden, nachdem all dieser PHP / MYSQL / PDO-Wahnsinn die Entwicklungsschaltung getroffen hat ... Ich glaube, es war Lerdorf selbst, der die Kontrolle hatte LIMIT-Implementierung vor einigen Jahren. Nein, es beantwortet die Frage nicht, aber es zeigt an, dass es sich um ein Aftermarket-Add-On handelt, und Sie wissen, wie gut sie manchmal funktionieren können ...
FredTheWebGuy
2
@ Ross PDO erlaubt keine Bindung an Werte - eher an Variablen. Wenn Sie bindParam (': Something', 2) versuchen, wird ein Fehler angezeigt, da PDO einen Zeiger auf die Variable verwendet, die eine Zahl nicht haben kann (wenn $ i 2 ist, können Sie einen Zeiger auf $ i haben, aber nicht auf die Nummer 2).
Kristijan
44

Die einfachste Lösung wäre, den Emulationsmodus auszuschalten. Sie können dies tun, indem Sie einfach die folgende Zeile hinzufügen

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

Dieser Modus kann auch als Konstruktorparameter beim Erstellen einer PDO-Verbindung festgelegt werden . Es könnte eine bessere Lösung sein, da einige berichten, dass ihr Treiber die setAttribute()Funktion nicht unterstützt .

Es wird nicht nur Ihr Problem mit der Bindung lösen, sondern Sie können auch Werte direkt an senden execute(), wodurch Ihr Code erheblich kürzer wird. Vorausgesetzt, der Emulationsmodus wurde bereits eingestellt, dauert die gesamte Angelegenheit bis zu einem halben Dutzend Codezeilen

$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);
Ihr gesunder Menschenverstand
quelle
SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes... Warum ist es für mich nie so einfach :) Obwohl ich sicher bin, dass dies die meisten Leute dorthin bringen wird, musste ich in meinem Fall etwas Ähnliches wie die akzeptierte Antwort verwenden. Nur ein Kopf hoch für zukünftige Leser!
Matthew Johnson
@ MatthewJohnson welcher Treiber ist es?
Ihr gesunder Menschenverstand
Ich bin nicht sicher, aber im Handbuch steht 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 ist neu für mich, aber andererseits fange ich gerade erst mit PDO an. Verwenden Sie normalerweise mysqli, aber ich dachte, ich würde versuchen, meinen Horizont zu erweitern.
Matthew Johnson
@MatthewJohnson Wenn Sie PDO für MySQL verwenden, unterstützt der Treiber diese Funktion in Ordnung. Also, Sie erhalten diese Nachricht aufgrund eines Fehlers
Ihr gesunder Menschenverstand
1
Wenn Sie eine Meldung zur Treiberunterstützung erhalten, überprüfen Sie erneut, ob Sie setAttributedie Anweisung ($ stm, $ stmt) und nicht das pdo-Objekt aufrufen.
Jehong Ahn
17

Wenn Sie sich den Fehlerbericht ansehen, könnte Folgendes funktionieren:

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

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

Aber sind Sie sicher, dass Ihre eingehenden Daten korrekt sind? Da in der Fehlermeldung, es scheint nur zu sein , ein Zitat nach der Nummer (in Bezug auf die gesamte Anzahl entgegengesetzt ist in Anführungszeichen eingeschlossen). Dies kann auch ein Fehler bei Ihren eingehenden Daten sein. Können Sie etwas tun print_r($_GET);, um es herauszufinden?

Pekka
quelle
1
'' 15 ', 15'. Die erste Zahl ist vollständig in Anführungszeichen eingeschlossen. Die zweite Zahl hat überhaupt keine Anführungszeichen. Also ja, die Daten sind gut.
Nathan H
8

Dies nur als Zusammenfassung.
Es gibt vier Optionen zur Parametrisierung von LIMIT / OFFSET-Werten:

  1. Deaktivieren Sie PDO::ATTR_EMULATE_PREPARESwie erwähnt oben .

    Dadurch wird verhindert, dass pro übergebene Werte ->execute([...])immer als Zeichenfolgen angezeigt werden.

  2. Wechseln Sie zur manuellen ->bindValue(..., ..., PDO::PARAM_INT)Parameterpopulation.

    Was jedoch weniger bequem ist als eine -> Ausführungsliste [].

  3. Machen Sie hier einfach eine Ausnahme und interpolieren Sie bei der Vorbereitung der SQL-Abfrage nur einfache Ganzzahlen.

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

    Das Casting ist wichtig. Häufiger ->prepare(sprintf("SELECT ... LIMIT %d", $num))wird dies für solche Zwecke verwendet.

  4. Wenn Sie nicht MySQL verwenden, sondern beispielsweise SQLite oder Postgres; Sie können gebundene Parameter auch direkt in SQL umwandeln.

     SELECT * FROM tbl LIMIT (1 * :limit)

    Auch hier unterstützt MySQL / MariaDB keine Ausdrücke in der LIMIT-Klausel. Noch nicht.

Mario
quelle
1
Ich hätte sprintf () mit% d für 3 verwendet, ich würde sagen, es ist etwas stabiler als mit der Variablen.
hakre
Ja, die Varfunc Cast + Interpolation ist nicht das praktischste Beispiel. Ich würde meine Faulheit oft {$_GET->int["limit"]}für solche Fälle verwenden.
Mario
7

zum LIMIT :init, :end

Sie müssen auf diese Weise binden. Wenn Sie so etwas hatten, wird $req->execute(Array());es nicht funktionieren, da es PDO::PARAM_STRauf alle Variablen im Array übertragen wird und für die LIMITSie unbedingt eine Ganzzahl benötigen. bindValue oder BindParam wie Sie möchten.

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

Da niemand erklärt hat, warum dies geschieht, füge ich eine Antwort hinzu. Der Grund dafür ist, dass Sie verwenden trim(). Wenn Sie sich das PHP-Handbuch ansehen trim, ist der Rückgabetyp string. Sie versuchen dann, dies als zu übergeben PDO::PARAM_INT. Einige Möglichkeiten, dies zu umgehen, sind:

  1. Verwenden filter_var($integer, FILTER_VALIDATE_NUMBER_INT)Sie diese Option , um sicherzustellen, dass Sie eine Ganzzahl übergeben.
  2. Wie andere sagten, mit intval()
  3. Casting mit (int)
  4. Überprüfen, ob es sich um eine Ganzzahl mit handelt is_int()

Es gibt viel mehr Möglichkeiten, aber dies ist im Grunde die Hauptursache.

Melissa Williams
quelle
3
Dies geschieht auch dann, wenn die Variable immer eine Ganzzahl war.
felwithe
1

bindValue Offset und Limit mit PDO :: PARAM_INT und es wird funktionieren

Karel
quelle
-1

// BEFORE (Present error) $ query = ".... LIMIT: p1, 30;"; ... $ stmt-> bindParam (': p1', $ limiteInferior);

// AFTER (Fehler korrigiert) $ query = ".... LIMIT: p1, 30;"; ... $ limiteInferior = (int) $ limiteInferior; $ stmt-> bindParam (': p1', $ limiteInferior, PDO :: PARAM_INT);

Brayan Josue Medina Melendez
quelle
-1

PDO::ATTR_EMULATE_PREPARES gab mir das

Der Treiber unterstützt diese Funktion nicht: Dieser Treiber unterstützt das Einstellen des Attributfehlers nicht.

Meine Problemumgehung bestand darin, eine $limitVariable als Zeichenfolge festzulegen und sie dann in der prepare-Anweisung wie im folgenden Beispiel zu kombinieren:

$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 ) {
    ...
}
Flossen
quelle
-1

Es gibt viel zwischen verschiedenen Versionen von PHP und den Kuriositäten von PDO. Ich habe hier 3 oder 4 Methoden ausprobiert, konnte LIMIT jedoch nicht zum Laufen bringen.
Mein Vorschlag ist, die Formatierung / Konkatination von Zeichenfolgen MIT einem intval () - Filter zu verwenden:

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

Es ist sehr wichtig, intval () zu verwenden, um eine SQL-Injection zu verhindern, insbesondere wenn Sie Ihr Limit von $ _GET oder ähnlichem erhalten. Wenn Sie dies tun, ist dies der einfachste Weg, um LIMIT zum Laufen zu bringen.

Es wird viel über 'Das Problem mit LIMIT in PDO' gesprochen, aber ich denke hier, dass PDO-Parameter nie für LIMIT verwendet werden sollten, da sie immer ganze Zahlen sind, funktioniert ein schneller Filter. Trotzdem ist es etwas irreführend, da die Philosophie immer darin bestand, keine SQL-Injection-Filterung selbst durchzuführen, sondern "PDO damit umgehen zu lassen".

Tycon
quelle