LIMIT yan tümcesinde bindValue yöntemi nasıl uygulanır?


117

İşte kodumun anlık görüntüsü:

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

alırım

SQL sözdiziminizde bir hata var; 1. satırda '' 15 ', 15' yakınında kullanılacak doğru sözdizimi için MySQL sunucu sürümünüze karşılık gelen kılavuza bakın

Görünüşe göre PDO, SQL kodunun LIMIT kısmındaki değişkenlerime tek tırnaklar ekliyor. Baktım, ilgili olduğunu düşündüğüm bu hatayı buldum: http://bugs.php.net/bug.php?id=44639

Baktığım şey bu mu? Bu hata Nisan 2008'den beri açıldı! Bu arada ne yapmamız gerekiyor?

Sql ifadesini göndermeden önce biraz sayfalandırma oluşturmam ve verilerin temiz, sql enjeksiyonu güvenli olduğundan emin olmam gerekiyor.



Yanıtlar:


165

Bu sorunu daha önce yaşadığımı hatırlıyorum. Bind işlevine iletmeden önce değeri bir tam sayıya çevirin. Sanırım bu sorunu çözüyor.

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

37
Teşekkürler! Ancak PHP 5.3'te, yukarıdaki kod "Ölümcül hata: Parametre 2'ye referansla geçilemiyor" diyen bir hata verdi. Orada int yapmaktan hoşlanmaz. Bunun yerine (int) trim($_GET['skip'])deneyin intval(trim($_GET['skip'])).
Will Martin

5
birisi bunun neden böyle olduğunu açıklasa harika olurdu ... tasarım / güvenlik (veya başka) açısından.
Ross

6
Bu, yalnızca öykünmüş hazırlanmış ifadeler etkinleştirilirse işe yarar . Devre dışı bırakılırsa başarısız olur (ve devre dışı bırakılması gerekir!)
Madara's Ghost

4
@Ross Bunu tam olarak yanıtlayamam - ancak LIMIT ve OFFSET'in tüm bu PHP / MYSQL / PDO çılgınlığının dev devresine çarpmasından SONRA yapıştırılmış özellikler olduğunu söyleyebilirim ... Aslında, denetleyenin Lerdorf olduğuna inanıyorum Birkaç yıl önce LIMIT uygulaması. Hayır, soruyu cevaplamıyor, ancak bunun bir satış sonrası eklentisi olduğunu gösteriyor ve bazen ne kadar iyi çalışabileceklerini biliyorsunuz ....
FredTheWebGuy

2
@Ross PDO değerlere bağlanmaya izin vermez - değişkenler yerine. BindParam'ı (': bir şey', 2) denerseniz, PDO bir sayının sahip olamayacağı değişkene bir işaretçi kullandığından bir hata alırsınız ($ i 2 ise $ i'ye doğru bir göstericiniz olabilir ancak 2 numara).
Kristijan

44

En basit çözüm, öykünme modunu kapatmak olacaktır. Aşağıdaki satırı ekleyerek yapabilirsiniz

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

Ayrıca bu mod, bir PDO bağlantısı oluştururken bir yapıcı parametre olarak ayarlanabilir . Bazıları sürücülerinin setAttribute()işlevi desteklemediğini bildirdiği için daha iyi bir çözüm olabilir .

Sorununuzu sadece bağlama ile çözmeyecek, aynı zamanda değerleri doğrudan içine göndermenize izin verecek execute()ve bu da kodunuzu önemli ölçüde kısaltacaktır. Öykünme modunun zaten ayarlandığını varsayarsak, tüm olay yarım düzine kod satırı alacaktır.

$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... Neden için asla çok basit benim :) Eminim bu orada çoğu insan alacak olduğum sürece, benim durumumda ben kullanımına kabul cevaba benzer bir şey sahip bitti. Gelecekteki okuyuculara bir uyarı!
Matthew Johnson

@MatthewJohnson ne sürücüsü?
Sizin Common Sense

Emin değilim ama kılavuzda yazıyor 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. Bu benim için yeni, ama sonra yine PDO'ya yeni başlıyorum. Genellikle mysqli kullanır, ancak ufkumu genişletmeye çalışacağımı düşündüm.
Matthew Johnson

@MatthewJohnson, mysql için PDO kullanıyorsanız, sürücü bu işlevi tamamen destekliyor. Yani, nedeniyle bazı hata bu mesajı alıyorsanız
Sizin Common Sense

1
Sürücü desteği sorun mesajı aldıysanız setAttribute, pdo nesnesi için değil ($ stm, $ stmt) ifadesini ararsanız tekrar kontrol edin .
Jehong Ahn

17

Hata raporuna bakıldığında, aşağıdakiler işe yarayabilir:

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

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

ama gelen verilerinizin doğru olduğundan emin misiniz? Çünkü hata mesajında, sayıdan sonra yalnızca bir alıntı var gibi görünüyor (tırnak içine alınan tam sayının aksine). Bu, gelen verilerinizle ilgili bir hata da olabilir. Öğrenmek için bir yapabilir misin print_r($_GET);?


1
"15", 15 ". İlk sayı tamamen tırnak içine alınmıştır. İkinci sayının hiç tırnak işareti yoktur. Yani evet, veriler iyi.
Nathan H

8

Bu sadece özet olarak.
LIMIT / OFFSET değerlerini parametrelendirmek için dört seçenek vardır:

  1. YukarıdaPDO::ATTR_EMULATE_PREPARES belirtildiği gibi devre dışı bırakın .

    Bu, per'e iletilen değerlerin ->execute([...])her zaman dizeler olarak görünmesini engeller .

  2. Manuel ->bindValue(..., ..., PDO::PARAM_INT)parametre doldurmaya geçin .

    Bununla birlikte, bir -> yürütme listesinden [] daha az kullanışlıdır.

  3. Basitçe burada bir istisna yapın ve SQL sorgusunu hazırlarken sadece düz tam sayıları hesaplayın.

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

    Oyuncu seçimi önemlidir. Daha yaygın olarak bu ->prepare(sprintf("SELECT ... LIMIT %d", $num))tür amaçlar için kullanıldığını görürsünüz .

  4. MySQL kullanmıyorsanız, ancak örneğin SQLite veya Postgres; ayrıca bağlı parametreleri doğrudan SQL'de de çevirebilirsiniz.

     SELECT * FROM tbl LIMIT (1 * :limit)

    Yine, MySQL / MariaDB, LIMIT cümlesindeki ifadeleri desteklemez. Henüz değil.


1
Ben 3 için% d ile sprintf () kullanırdım, değişkene göre biraz daha kararlı olduğunu söyleyebilirim.
hakre

Evet, varfunc cast + interpolasyonu en pratik örnek değil. Bu {$_GET->int["limit"]}tür durumlarda sık sık tembelliğimi kullanırdım .
mario

7

için LIMIT :init, :end

Bu şekilde bağlamalısın. eğer böyle bir şeye sahipseniz $req->execute(Array());çalışmaz çünkü PDO::PARAM_STRdizideki tüm değişkenlere atılır ve bunun için LIMITkesinlikle bir Tamsayıya ihtiyacınız vardır. bindValue veya BindParam istediğiniz gibi.

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

2

Bunun neden olduğunu kimse açıklamadığı için bir cevap ekliyorum. Böyle davranmasının nedeni, kullanıyor olmanızdır trim(). PHP kılavuzuna bakarsanız trim, dönüş türü string. Daha sonra bunu olarak geçmeye çalışıyorsunuz PDO::PARAM_INT. Bunu aşmanın birkaç yolu:

  1. filter_var($integer, FILTER_VALIDATE_NUMBER_INT)Bir tamsayı geçirdiğinizden emin olmak için kullanın .
  2. Başkalarının dediği gibi intval()
  3. İle yayınlanıyor (int)
  4. İle bir tam sayı olup olmadığını kontrol etmek is_int()

Daha birçok yol var, ancak temelde temel neden budur.


3
Değişken her zaman bir tamsayı olsa bile olur.
felwithe

1

bindValue ofset ve limit PDO :: PARAM_INT kullanarak çalışır ve çalışacaktır


-1

// ÖNCE (Mevcut hata) $ query = ".... LIMIT: p1, 30;"; ... $ stmt-> bindParam (': p1', $ limiteInferior);

// SONRA (Hata düzeltildi) $ query = ".... LIMIT: p1, 30;"; ... $ limiteInferior = (int) $ limiteInferior; $ stmt-> bindParam (': p1', $ limiteInferior, PDO :: PARAM_INT);


-1

PDO::ATTR_EMULATE_PREPARES bana verdi

Sürücü bu işlevi desteklemiyor: Bu sürücü özniteliklerin ayarlanması hatasını desteklemiyor.

Çözümüm, bir $limitdeğişkeni dize olarak ayarlamak ve ardından aşağıdaki örnekte olduğu gibi hazırla ifadesinde birleştirmekti:

$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

PHP'nin farklı sürümleri ile PDO'nun tuhaflıkları arasında pek çok şey var. Burada 3 veya 4 yöntem denedim ancak LIMIT çalışamadı.
Önerim, bir intval () filtresi İLE dize biçimlendirme / bitiştirme kullanmaktır :

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

SQL enjeksiyonunu önlemek için intval () kullanmak çok önemlidir, özellikle de limitinizi $ _GET veya benzerlerinden alıyorsanız. Bunu yaparsanız, LIMIT çalışmayı sağlamanın en kolay yolu budur.

'PDO'da LIMIT ile ilgili sorun' hakkında çok fazla konuşma var ama benim düşüncem, PDO parametrelerinin LIMIT için hiçbir zaman kullanılmaması gerektiğidir, çünkü bunlar her zaman tamsayılar olacaklardır ve hızlı bir filtre çalışır. Yine de, felsefe her zaman herhangi bir SQL enjeksiyonunu kendiniz filtrelemek yerine 'PDO'nun halletmesini sağlayın' olduğu için biraz yanıltıcıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.