จะใช้วิธีการ bindValue ในประโยค LIMIT ได้อย่างไร


117

นี่คือภาพรวมของรหัสของฉัน:

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

ฉันเข้าใจ

คุณมีข้อผิดพลาดในไวยากรณ์ SQL ของคุณ ตรวจสอบคู่มือที่ตรงกับเวอร์ชันเซิร์ฟเวอร์ MySQL ของคุณสำหรับไวยากรณ์ที่เหมาะสมที่จะใช้ใกล้ '' 15 ', 15' ที่บรรทัด 1

ดูเหมือนว่า PDO กำลังเพิ่มเครื่องหมายคำพูดเดี่ยวให้กับตัวแปรของฉันในส่วน LIMIT ของรหัส SQL ฉันค้นหามันพบข้อผิดพลาดนี้ซึ่งฉันคิดว่าเกี่ยวข้อง: http://bugs.php.net/bug.php?id=44639

นั่นคือสิ่งที่ฉันกำลังมองหาใช่ไหม จุดบกพร่องนี้ได้เปิดให้บริการตั้งแต่เมษายน 2551! เราควรทำอะไรในระหว่างนี้?

ฉันต้องการสร้างเลขหน้าและต้องการให้แน่ใจว่าข้อมูลนั้นสะอาดปลอดภัยในการฉีด sql ก่อนที่จะส่งคำสั่ง sql



คำตอบที่น่าสังเกตในคำถามที่ซ้ำกัน: การสืบค้น PDO แบบ Parametrized และประโยค "LIMIT` - ไม่ทำงาน [ซ้ำ] (ส.ค. 2013 โดย Bill Karwin)
hakre

คำตอบ:


165

ฉันจำได้ว่าเคยมีปัญหานี้มาก่อน แคสต์ค่าเป็นจำนวนเต็มก่อนส่งต่อไปยังฟังก์ชันการโยง ฉันคิดว่ามันช่วยแก้ปัญหาได้

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

37
ขอบคุณ! แต่ใน PHP 5.3 โค้ดด้านบนทำให้เกิดข้อผิดพลาดว่า "Fatal error: Cannot pass parameter 2 by reference" มันไม่ชอบการแคสต์ int ที่นั่น แทนที่จะพยายาม(int) trim($_GET['skip']) intval(trim($_GET['skip']))
Will Martin

5
จะดีมากถ้ามีคนให้คำอธิบายว่าทำไมถึงเป็นเช่นนั้น ... จากมุมมองด้านการออกแบบ / ความปลอดภัย (หรืออื่น ๆ )
รอส

6
นี้จะทำงานถ้าเทิดทูนงบเตรียมจะเปิดใช้งาน มันจะล้มเหลวหากปิดใช้งาน (และควรปิดการใช้งาน!)
Madara's Ghost

4
@Ross ฉันไม่สามารถตอบสิ่งนี้ได้โดยเฉพาะ - แต่ฉันสามารถชี้ให้เห็นว่า LIMIT และ OFFSET เป็นคุณสมบัติที่ติดกาวหลังจากความบ้าคลั่งของ PHP / MYSQL / PDO ทั้งหมดนี้เข้าสู่วงจร dev ... ในความเป็นจริงฉันเชื่อว่า Lerdorf เองที่ดูแล LIMIT การใช้งานไม่กี่ปีที่ผ่านมา ไม่มันไม่ได้ตอบคำถาม แต่มันบ่งบอกว่ามันเป็นส่วนเสริมหลังการขายและคุณรู้ว่าบางครั้งพวกเขาสามารถทำงานได้ดีเพียงใด ....
FredTheWebGuy

2
@Ross PDO ไม่อนุญาตให้เชื่อมโยงกับค่า - แทนที่จะเป็นตัวแปร หากคุณลอง bindParam (': something', 2) คุณจะมีข้อผิดพลาดเนื่องจาก PDO ใช้ตัวชี้ไปยังตัวแปรที่ไม่มีตัวเลข (ถ้า $ i คือ 2 คุณสามารถมีตัวชี้ไปทาง $ i แต่ไม่ไปทาง หมายเลข 2)
Kristijan

44

วิธีแก้ปัญหาที่ง่ายที่สุดคือปิดโหมดจำลอง คุณสามารถทำได้โดยเพียงเพิ่มบรรทัดต่อไปนี้

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

นอกจากนี้โหมดนี้สามารถตั้งค่าเป็นพารามิเตอร์ตัวสร้างเมื่อสร้างการเชื่อมต่อ PDO อาจเป็นทางออกที่ดีกว่าเนื่องจากบางคนรายงานว่าไดรเวอร์ไม่รองรับsetAttribute()ฟังก์ชันนี้

ไม่เพียง แต่แก้ปัญหาของคุณด้วยการผูก แต่ยังช่วยให้คุณส่งค่าเข้าโดยตรงexecute()ซึ่งจะทำให้โค้ดของคุณสั้นลงอย่างมาก สมมติว่ามีการตั้งค่าโหมดการจำลองแล้วเรื่องทั้งหมดจะใช้รหัสมากถึงครึ่งโหล

$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... ทำไมมันไม่ง่ายสำหรับฉันเลย :) แม้ว่าฉันแน่ใจว่าสิ่งนี้จะทำให้คนส่วนใหญ่ไปที่นั่น แต่ในกรณีของฉันฉันก็ต้องใช้คำตอบที่คล้ายกับคำตอบที่ยอมรับ เพียงแค่แจ้งผู้อ่านในอนาคต!
Matthew Johnson

@MatthewJohnson มันคือไดรเวอร์อะไร?
สามัญสำนึกของคุณ

ผมไม่แน่ใจ แต่ในคู่มือ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ก็กล่าวว่า เป็นเรื่องใหม่สำหรับฉัน แต่แล้วอีกครั้งฉันเพิ่งเริ่มต้นกับ PDO โดยปกติจะใช้ mysqli แต่คิดว่าฉันจะพยายามขยายขอบเขตอันไกลโพ้น
Matthew Johnson

@MatthewJohnson หากคุณใช้ PDO สำหรับ mysql ไดรเวอร์จะรองรับฟังก์ชันนี้ทั้งหมด คุณได้รับข้อความนี้เนื่องจากความผิดพลาด
สามัญสำนึกของคุณ

1
หากคุณได้รับข้อความแจ้งปัญหาการสนับสนุนไดรเวอร์ให้ตรวจสอบอีกครั้งว่าคุณเรียกsetAttributeใช้คำสั่ง ($ stm, $ stmt) ไม่ใช่สำหรับวัตถุ pdo
Jehong Ahn

17

ดูรายงานข้อบกพร่องสิ่งต่อไปนี้อาจใช้ได้:

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

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

แต่แน่ใจหรือว่าข้อมูลขาเข้าของคุณถูกต้อง เนื่องจากในข้อความแสดงข้อผิดพลาดดูเหมือนว่าจะมีเครื่องหมายคำพูดเพียงครั้งเดียวหลังตัวเลข (ตรงข้ามกับจำนวนเต็มที่อยู่ในเครื่องหมายคำพูด) นี่อาจเป็นข้อผิดพลาดกับข้อมูลขาเข้าของคุณ คุณสามารถprint_r($_GET);หาคำตอบได้หรือไม่?


1
'' 15 ', 15' หมายเลขแรกอยู่ในเครื่องหมายคำพูด ตัวเลขที่สองไม่มีเครื่องหมายคำพูดเลย ใช่ข้อมูลดี
Nathan H

8

นี่เป็นเพียงบทสรุป
มีสี่ตัวเลือกในการกำหนดพารามิเตอร์ค่า LIMIT / OFFSET:

  1. ปิดการใช้งานPDO::ATTR_EMULATE_PREPARESตามที่กล่าวไว้ข้างต้น

    ซึ่งป้องกันไม่ให้ค่าที่ส่งต่อ->execute([...])เพื่อแสดงเป็นสตริงเสมอ

  2. เปลี่ยนเป็น->bindValue(..., ..., PDO::PARAM_INT)ค่าพารามิเตอร์ที่กำหนดเอง

    อย่างไรก็ตามซึ่งสะดวกน้อยกว่า -> execute list []

  3. เพียงสร้างข้อยกเว้นที่นี่และเพียงแค่แก้ไขจำนวนเต็มธรรมดาเมื่อเตรียมแบบสอบถาม SQL

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

    ความหล่อเป็นสิ่งสำคัญ โดยทั่วไปคุณเห็นว่า->prepare(sprintf("SELECT ... LIMIT %d", $num))ใช้เพื่อวัตถุประสงค์ดังกล่าว

  4. หากคุณไม่ได้ใช้ MySQL แต่เป็นตัวอย่างเช่น SQLite หรือ Postgres คุณยังสามารถส่งพารามิเตอร์ที่ถูกผูกไว้ใน SQL ได้โดยตรง

     SELECT * FROM tbl LIMIT (1 * :limit)

    อีกครั้ง MySQL / MariaDB ไม่รองรับนิพจน์ในประโยค LIMIT ยัง.


1
ฉันจะใช้ sprintf () กับ% d สำหรับ 3 ฉันบอกว่ามันเสถียรกว่าเล็กน้อยแล้วกับตัวแปร
hakre

ใช่การแก้ไข varfunc + การแก้ไขไม่ใช่ตัวอย่างที่ใช้ได้จริง ฉันมักจะใช้ความขี้เกียจของฉัน{$_GET->int["limit"]}ในกรณีเช่นนี้
มาริโอ

7

สำหรับ LIMIT :init, :end

คุณต้องผูกมัดด้วยวิธีนั้น หากคุณมีบางอย่างที่$req->execute(Array());ไม่สามารถใช้งานได้เนื่องจากมันจะส่งPDO::PARAM_STRไปยัง vars ทั้งหมดในอาร์เรย์และสำหรับสิ่งที่LIMITคุณต้องการจำนวนเต็ม bindValue หรือ BindParam ตามที่คุณต้องการ

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

2

เนื่องจากไม่มีใครอธิบายว่าเหตุใดจึงเกิดขึ้นฉันจึงเพิ่มคำตอบ trim()เหตุผลที่มีพฤติกรรมนี้ก็เป็นเพราะคุณกำลังใช้ ถ้าคุณดูที่คู่มือ PHP สำหรับพิมพ์ส่งกลับเป็นtrim แล้วคุณกำลังพยายามที่จะผ่านนี้เป็นstring PDO::PARAM_INTสองสามวิธีในการแก้ไขปัญหานี้ ได้แก่ :

  1. ใช้filter_var($integer, FILTER_VALIDATE_NUMBER_INT)เพื่อให้แน่ใจว่าคุณกำลังส่งจำนวนเต็ม
  2. อย่างที่คนอื่น ๆ บอกโดยใช้ intval()
  3. หล่อด้วย (int)
  4. กำลังตรวจสอบว่าเป็นจำนวนเต็มด้วยหรือไม่ is_int()

ยังมีอีกหลายวิธี แต่นี่คือสาเหตุที่แท้จริง


3
มันเกิดขึ้นแม้ว่าตัวแปรจะเป็นจำนวนเต็มเสมอก็ตาม
felwithe


-1

// ก่อน (ข้อผิดพลาดในปัจจุบัน) $ query = ".... LIMIT: p1, 30;"; ... $ stmt-> bindParam (': p1', $ limiteInferior);

// AFTER (แก้ไขข้อผิดพลาด) $ query = ".... LIMIT: p1, 30;"; ... $ limiteInferior = (int) $ limiteInferior; $ stmt-> bindParam (': p1', $ limiteInferior, PDO :: PARAM_INT);


-1

PDO::ATTR_EMULATE_PREPARES ให้ฉัน

ไดรเวอร์ไม่รองรับฟังก์ชันนี้: ไดรเวอร์นี้ไม่รองรับข้อผิดพลาดของการตั้งค่าแอตทริบิวต์

วิธีแก้ปัญหาของฉันคือการตั้งค่า$limitตัวแปรเป็นสตริงจากนั้นรวมเข้ากับคำสั่งเตรียมดังตัวอย่างต่อไปนี้:

$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 เวอร์ชันต่างๆและความแปลกประหลาดของ PDO ฉันลอง 3 หรือ 4 วิธีที่นี่ แต่ไม่สามารถทำให้ LIMIT ทำงานได้
คำแนะนำของฉันคือการใช้การจัดรูปแบบสตริง / การเชื่อมต่อกับตัวกรองintval () :

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

เป็นสิ่งสำคัญมากที่จะต้องใช้ intval () เพื่อป้องกันการแทรก SQL โดยเฉพาะอย่างยิ่งถ้าคุณได้รับขีด จำกัด จาก $ _GET หรือสิ่งที่คล้ายกัน หากคุณทำเช่นนั้นนี่เป็นวิธีที่ง่ายที่สุดในการทำให้ LIMIT ทำงาน

มีการพูดคุยกันมากมายเกี่ยวกับ 'ปัญหากับ LIMIT ใน PDO' แต่ความคิดของฉันที่นี่คือ PDO params ไม่เคยถูกนำมาใช้กับ LIMIT เนื่องจากมันจะเป็นจำนวนเต็มเสมอการกรองอย่างรวดเร็ว ยังคงเป็นเรื่องที่ทำให้เข้าใจผิดเล็กน้อยเนื่องจากปรัชญามักจะไม่ทำการกรอง SQL injection ใด ๆ ด้วยตัวคุณเอง แต่ให้ 'ให้ PDO จัดการกับมัน'

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.