Come applicare il metodo bindValue nella clausola LIMIT?


117

Ecco un'istantanea del mio codice:

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

ottengo

Hai un errore nella sintassi SQL; controlla il manuale che corrisponde alla versione del tuo server MySQL per la sintassi corretta da usare vicino a '' 15 ', 15' alla riga 1

Sembra che PDO stia aggiungendo virgolette singole alle mie variabili nella parte LIMIT del codice SQL. Ho cercato ho trovato questo bug che penso sia correlato: http://bugs.php.net/bug.php?id=44639

È quello che sto guardando? Questo bug è stato aperto da aprile 2008! Cosa dovremmo fare nel frattempo?

Devo creare un po 'di impaginazione e devo assicurarmi che i dati siano puliti, sql injection-safe, prima di inviare l'istruzione sql.



Risposte:


165

Ricordo di aver avuto questo problema prima. Trasmetti il ​​valore a un numero intero prima di passarlo alla funzione bind. Penso che questo lo risolva.

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

37
Grazie! Ma in PHP 5.3, il codice sopra ha generato un errore che diceva "Errore irreversibile: Impossibile passare il parametro 2 per riferimento". Non gli piace lanciare un int lì. Invece di (int) trim($_GET['skip'])provare intval(trim($_GET['skip'])).
Will Martin l'

5
Sarebbe bello se qualcuno fornisse la spiegazione del perché è così ... dal punto di vista del design / sicurezza (o altro).
Ross

6
Funzionerà solo se sono abilitate le istruzioni preparate emulate . Fallirà se è disabilitato (e dovrebbe essere disabilitato!)
Madara's Ghost

4
@ Ross non posso rispondere in modo specifico a questo, ma posso sottolineare che LIMIT e OFFSET sono caratteristiche che sono state incollate DOPO che tutta questa follia PHP / MYSQL / PDO ha colpito il circuito di sviluppo ... In effetti, credo che sia stato lo stesso Lerdorf a supervisionare Implementazione LIMIT qualche anno fa. No, non risponde alla domanda, ma indica che si tratta di un componente aggiuntivo aftermarket e sai quanto bene possono funzionare a volte ...
FredTheWebGuy

2
@Ross PDO non consente l'associazione a valori, piuttosto variabili. Se provi bindParam (': something', 2) avrai un errore poiché PDO usa un puntatore alla variabile che un numero non può avere (se $ i è 2 puoi avere un puntatore verso $ i ma non verso numero 2).
Kristijan

44

La soluzione più semplice sarebbe disattivare la modalità di emulazione. Puoi farlo semplicemente aggiungendo la seguente riga

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

Inoltre, questa modalità può essere impostata come parametro del costruttore durante la creazione di una connessione PDO . Potrebbe essere una soluzione migliore in quanto alcuni segnalano che il loro driver non supporta la setAttribute()funzione.

Non solo risolverà il tuo problema con l'associazione, ma ti consentirà anche di inviare valori direttamente a execute(), il che renderà il tuo codice notevolmente più breve. Supponendo che la modalità di emulazione sia già stata impostata, l'intera faccenda richiederà fino a mezza dozzina di righe di codice

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

SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes... Perché non è mai così semplice per me :) Mentre sono sicuro che questo porterà la maggior parte delle persone lì, nel mio caso ho finito per dover usare qualcosa di simile alla risposta accettata. Solo un avvertimento ai futuri lettori!
Matthew Johnson

@ MatthewJohnson che driver è?
Il tuo buon senso

Non ne sono sicuro, ma nel manuale c'è scritto 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. È nuovo per me, ma ancora una volta ho appena iniziato con PDO. Di solito uso mysqli, ma ho pensato di provare ad ampliare i miei orizzonti.
Matthew Johnson,

@ MatthewJohnson se stai usando PDO per mysql, il driver supporta questa funzione. Quindi, stai ricevendo questo messaggio a causa di un errore
Il tuo buon senso

1
Se hai ricevuto un messaggio di problema con il supporto del driver, controlla di nuovo se chiami setAttributeper l'istruzione ($ stm, $ stmt) non per l'oggetto pdo.
Jehong Ahn

17

Guardando la segnalazione di bug, potrebbe funzionare quanto segue:

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

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

ma sei sicuro che i tuoi dati in arrivo siano corretti? Perché nel messaggio di errore, sembra esserci solo una virgoletta dopo il numero (a differenza dell'intero numero racchiuso tra virgolette). Potrebbe anche trattarsi di un errore con i dati in arrivo. Puoi fare print_r($_GET);per scoprirlo?


1
"15", 15 ". Il primo numero è completamente racchiuso tra virgolette. Il secondo numero non ha affatto virgolette. Quindi sì, i dati sono buoni.
Nathan H,

8

Questo solo come sommario.
Sono disponibili quattro opzioni per parametrizzare i valori LIMIT / OFFSET:

  1. Disabilita PDO::ATTR_EMULATE_PREPAREScome menzionato sopra .

    Ciò impedisce che i valori passati per ->execute([...])vengano visualizzati sempre come stringhe.

  2. Passa al ->bindValue(..., ..., PDO::PARAM_INT)popolamento manuale dei parametri.

    Che però è meno conveniente di un -> execute list [].

  3. Basta fare un'eccezione qui e interpolare interi semplici quando si prepara la query SQL.

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

    Il casting è importante. Più comunemente vedi ->prepare(sprintf("SELECT ... LIMIT %d", $num))usato per tali scopi.

  4. Se non stai usando MySQL, ma ad esempio SQLite o Postgres; puoi anche eseguire il cast di parametri associati direttamente in SQL.

     SELECT * FROM tbl LIMIT (1 * :limit)

    Di nuovo, MySQL / MariaDB non supportano le espressioni nella clausola LIMIT. Non ancora.


1
Avrei usato sprintf () con% d per 3, direi che è un po 'più stabile rispetto alla variabile.
hakre

Sì, il cast di varfunc + l'interpolazione non è l'esempio più pratico. Spesso userei il mio pigro {$_GET->int["limit"]}per questi casi.
mario

7

per LIMIT :init, :end

Devi legarti in questo modo. se avessi qualcosa del genere $req->execute(Array());non funzionerà in quanto verrà PDO::PARAM_STReseguito il cast su tutte le variabili dell'array e per il LIMIThai assolutamente bisogno di un numero intero. bindValue o BindParam come desideri.

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

2

Poiché nessuno ha spiegato perché questo sta accadendo, aggiungo una risposta. Il motivo per cui si sta comportando in questo modo è perché stai usando trim(). Se guardi il manuale PHP per trim, il tipo restituito è string. Stai quindi cercando di passare questo come PDO::PARAM_INT. Alcuni modi per aggirare questo problema sono:

  1. Usa filter_var($integer, FILTER_VALIDATE_NUMBER_INT)per assicurarti di passare un numero intero.
  2. Come altri hanno detto, utilizzando intval()
  3. Casting con (int)
  4. Verificare se è un numero intero con is_int()

Ci sono molti altri modi, ma questa è fondamentalmente la causa principale.


3
Succede anche quando la variabile è sempre stata un numero intero.
felwithe l'

1

bindValue offset e limite utilizzando PDO :: PARAM_INT e funzionerà


-1

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

// DOPO (Errore corretto) $ query = ".... LIMIT: p1, 30;"; ... $ limiteInferior = (int) $ limiteInferior; $ stmt-> bindParam (': p1', $ limiteInferior, PDO :: PARAM_INT);


-1

PDO::ATTR_EMULATE_PREPARES mi ha dato il

Il driver non supporta questa funzione: questo driver non supporta l'errore di impostazione degli attributi.

La mia soluzione alternativa era impostare una $limitvariabile come stringa, quindi combinarla nell'istruzione prepare come nell'esempio seguente:

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

-1

C'è molto da fare tra le diverse versioni di PHP e le stranezze di PDO. Ho provato 3 o 4 metodi qui ma non sono riuscito a far funzionare LIMIT.
Il mio suggerimento è di utilizzare la formattazione / concatinazione delle stringhe CON un filtro intval () :

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

È molto importante utilizzare intval () per prevenire l'iniezione SQL, in particolare se ottieni il tuo limite da $ _GET o simili. Se lo fai, questo è il modo più semplice per far funzionare LIMIT.

Si parla molto di "Il problema con LIMIT in PDO" ma il mio pensiero qui è che i parametri PDO non sono mai stati utilizzati per LIMIT poiché saranno sempre numeri interi, un filtro rapido funziona. Tuttavia, è un po 'fuorviante poiché la filosofia è sempre stata quella di non eseguire alcun filtro SQL injection da soli, ma piuttosto di "far gestire a PDO".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.