Bagaimana cara menerapkan metode bindValue di klausa LIMIT?

117

Ini adalah cuplikan dari kode saya:

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

saya mendapat

Ada kesalahan dalam sintaks SQL Anda; periksa manual yang sesuai dengan versi server MySQL Anda untuk sintaks yang benar untuk digunakan di dekat '' 15 ', 15' di baris 1

Tampaknya PDO menambahkan tanda kutip tunggal ke variabel saya di bagian LIMIT dari kode SQL. Saya mencarinya Saya menemukan bug ini yang menurut saya terkait: http://bugs.php.net/bug.php?id=44639

Itukah yang saya lihat? Bug ini telah dibuka sejak April 2008! Apa yang harus kami lakukan sementara itu?

Saya perlu membuat beberapa pagination, dan perlu memastikan datanya bersih, sql injection-safe, sebelum mengirim pernyataan sql.

Nathan H.
sumber

Jawaban:

165

Saya ingat pernah mengalami masalah ini sebelumnya. Transmisikan nilai ke integer sebelum meneruskannya ke fungsi bind. Saya pikir ini menyelesaikannya.

$fetchPictures->bindValue(':skip', (int) trim($_GET['skip']), PDO::PARAM_INT);
Stephen Curran
sumber
37
Terima kasih! Namun di PHP 5.3, kode di atas memunculkan kesalahan yang mengatakan "Kesalahan fatal: Tidak dapat melewatkan parameter 2 dengan referensi". Itu tidak seperti mentransmisikan int di sana. Alih-alih (int) trim($_GET['skip']), coba intval(trim($_GET['skip'])).
Will Martin
5
akan keren jika seseorang memberikan penjelasan mengapa ini begitu ... dari sudut pandang desain / keamanan (atau lainnya).
Ross
6
Ini hanya akan berfungsi jika pernyataan siap yang diemulasi diaktifkan . Ini akan gagal jika dinonaktifkan (dan harus dinonaktifkan!)
Hantu Madara
4
@Ross Saya tidak dapat secara khusus menjawab ini- tetapi saya dapat menunjukkan bahwa LIMIT dan OFFSET adalah fitur yang terpaku SETELAH semua kegilaan PHP / MYSQL / PDO ini menghantam sirkuit dev ... Sebenarnya, saya yakin Lerdorf sendirilah yang mengawasi LIMIT implementasi beberapa tahun yang lalu. Tidak, itu tidak menjawab pertanyaan, tetapi itu menunjukkan bahwa ini adalah add-on aftermarket, dan Anda tahu seberapa baik mereka kadang-kadang bisa bekerja ....
FredTheWebGuy
2
@Ross PDO tidak mengizinkan pengikatan terhadap nilai - melainkan variabel. Jika Anda mencoba bindParam (': sesuatu', 2) Anda akan mengalami kesalahan karena PDO menggunakan pointer ke variabel yang tidak dapat dimiliki oleh angka (jika $ i adalah 2 Anda dapat memiliki pointer ke $ i tetapi tidak ke arah nomor 2).
Kristijan
44

Solusi paling sederhana adalah dengan mematikan mode emulasi. Anda dapat melakukannya hanya dengan menambahkan baris berikut

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

Selain itu, mode ini dapat disetel sebagai parameter konstruktor saat membuat koneksi PDO . Ini bisa menjadi solusi yang lebih baik karena beberapa melaporkan bahwa driver mereka tidak mendukung setAttribute()fungsi tersebut.

Ini tidak hanya akan menyelesaikan masalah Anda dengan pengikatan, tetapi juga memungkinkan Anda mengirim nilai secara langsung execute(), yang akan membuat kode Anda lebih pendek secara dramatis. Dengan asumsi mode emulasi telah disetel, keseluruhan urusan akan mengambil sebanyak setengah lusin baris kode

$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);
Akal Sehat Anda
sumber
SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes... Mengapa tidak pernah sesederhana itu bagi saya :) Meskipun saya yakin ini akan membuat kebanyakan orang di sana, dalam kasus saya, saya akhirnya harus menggunakan sesuatu yang mirip dengan jawaban yang diterima. Hanya kepala untuk pembaca yang akan datang!
Matthew Johnson
@MatthewJohnson driver apa itu?
Akal Sehat Anda
Saya tidak yakin, tapi di manual tertulis 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. Ini hal baru bagi saya, tapi sekali lagi saya baru mulai dengan PDO. Biasanya menggunakan mysqli, tapi saya pikir saya akan mencoba memperluas wawasan saya.
Matthew Johnson
@MatthewJohnson jika Anda menggunakan PDO untuk mysql, driver tidak mendukung fungsi ini. Jadi, Anda menerima pesan ini karena suatu kesalahan
Akal Sehat Anda
1
Jika Anda mendapat pesan masalah dukungan driver, periksa lagi apakah Anda memanggil setAttributepernyataan ($ stm, $ stmt) bukan untuk objek pdo.
Jehong Ahn
17

Melihat laporan bug, berikut ini mungkin berhasil:

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

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

tetapi apakah Anda yakin data yang masuk sudah benar? Karena dalam pesan kesalahan, sepertinya hanya ada satu kutipan setelah nomor (berlawanan dengan seluruh angka yang diapit tanda kutip). Ini juga bisa menjadi kesalahan dengan data masuk Anda. Bisakah Anda melakukan print_r($_GET);untuk mencari tahu?

Pekka
sumber
1
'' 15 ', 15'. Angka pertama diapit tanda kutip sepenuhnya. Angka kedua tidak memiliki tanda kutip sama sekali. Jadi ya, datanya bagus.
Nathan H
8

Ini hanya sebagai ringkasan.
Ada empat opsi untuk membuat parameter nilai LIMIT / OFFSET:

  1. Nonaktifkan PDO::ATTR_EMULATE_PREPARESseperti yang disebutkan di atas .

    Yang mencegah nilai yang diteruskan per ->execute([...])untuk selalu muncul sebagai string.

  2. Beralih ke ->bindValue(..., ..., PDO::PARAM_INT)populasi parameter manual .

    Yang bagaimanapun kurang nyaman daripada -> jalankan daftar [].

  3. Cukup buat pengecualian di sini dan cukup interpolasi bilangan bulat biasa saat menyiapkan kueri SQL.

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

    Pengecoran itu penting. Lebih umum Anda lihat ->prepare(sprintf("SELECT ... LIMIT %d", $num))digunakan untuk tujuan seperti itu.

  4. Jika Anda tidak menggunakan MySQL, tetapi misalnya SQLite, atau Postgres; Anda juga dapat mentransmisikan parameter terikat langsung di SQL.

     SELECT * FROM tbl LIMIT (1 * :limit)

    Sekali lagi, MySQL / MariaDB tidak mendukung ekspresi di klausa LIMIT. Belum.

mario
sumber
1
Saya akan menggunakan sprintf () dengan% d untuk 3, saya akan mengatakan itu sedikit lebih stabil daripada dengan variabel.
hakre
Ya, pemeran varfunc + interpolasi bukanlah contoh yang paling praktis. Saya sering menggunakan malas saya {$_GET->int["limit"]}untuk kasus seperti itu.
mario
7

untuk LIMIT :init, :end

Anda perlu mengikat seperti itu. jika Anda memiliki sesuatu seperti $req->execute(Array());itu tidak akan berfungsi karena itu akan dilemparkan PDO::PARAM_STRke semua vars dalam array dan LIMITAnda benar-benar membutuhkan Integer. bindValue atau BindParam sesuai keinginan Anda.

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

Karena tidak ada yang menjelaskan mengapa ini terjadi, saya menambahkan jawaban. Alasan mengapa berperilaku ini adalah karena Anda sedang menggunakan trim(). Jika Anda melihat manual PHP trim, jenis kembaliannya adalah string. Anda kemudian mencoba untuk melewatkan ini sebagai PDO::PARAM_INT. Beberapa cara untuk menyiasatinya adalah:

  1. Gunakan filter_var($integer, FILTER_VALIDATE_NUMBER_INT)untuk memastikan Anda meneruskan integer.
  2. Seperti yang dikatakan orang lain, menggunakan intval()
  3. Mentransmisikan dengan (int)
  4. Memeriksa apakah itu bilangan bulat dengan is_int()

Ada lebih banyak cara, tetapi ini pada dasarnya adalah akar penyebabnya.

Melissa Williams
sumber
3
Itu terjadi bahkan ketika variabel selalu berupa bilangan bulat.
felwithe
1

bindValue offset dan limit menggunakan PDO :: PARAM_INT dan itu akan berhasil

Karel
sumber
-1

// SEBELUM (Kesalahan saat ini) $ query = ".... LIMIT: p1, 30;"; ... $ stmt-> bindParam (': p1', $ limiteInferior);

// SETELAH (Error dikoreksi) $ query = ".... LIMIT: p1, 30;"; ... $ limiteInferior = (int) $ limiteInferior; $ stmt-> bindParam (': p1', $ limiteInferior, PDO :: PARAM_INT);

Brayan Josue Medina Melendez
sumber
-1

PDO::ATTR_EMULATE_PREPARES memberi saya

Driver tidak mendukung fungsi ini: Driver ini tidak mendukung kesalahan atribut pengaturan.

Solusi saya adalah menetapkan $limitvariabel sebagai string, lalu menggabungkannya dalam pernyataan persiapan seperti pada contoh berikut:

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

Ada banyak hal yang terjadi di antara berbagai versi PHP dan keanehan PDO. Saya mencoba 3 atau 4 metode di sini tetapi tidak berhasil LIMIT.
Saran saya adalah menggunakan pemformatan / penggabungan string DENGAN filter intval () :

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

Sangat penting untuk menggunakan intval () untuk mencegah injeksi SQL, terutama jika Anda mendapatkan batas dari $ _GET atau sejenisnya. Jika Anda melakukannya, ini adalah cara termudah untuk membuat LIMIT bekerja.

Ada banyak pembicaraan tentang 'Masalah dengan LIMIT di PDO' tetapi pemikiran saya di sini adalah bahwa parameter PDO tidak pernah digunakan untuk LIMIT karena mereka selalu akan menjadi bilangan bulat dan filter cepat berfungsi. Namun, ini agak menyesatkan karena filosofi selalu untuk tidak melakukan penyaringan injeksi SQL sendiri melainkan 'Minta PDO menanganinya'.

Tycon
sumber