Làm thế nào để áp dụng phương thức bindValue trong mệnh đề LIMIT?


117

Đây là ảnh chụp nhanh mã của tôi:

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

tôi có

Bạn có lỗi trong cú pháp SQL của mình; kiểm tra hướng dẫn sử dụng tương ứng với phiên bản máy chủ MySQL của bạn để biết cú pháp phù hợp để sử dụng gần '' 15 ', 15' ở dòng 1

Có vẻ như PDO đang thêm dấu ngoặc kép vào các biến của tôi trong phần LIMIT của mã SQL. Tôi đã tra cứu và tìm thấy lỗi này mà tôi nghĩ là có liên quan: http://bugs.php.net/bug.php?id=44639

Đó là những gì tôi đang nhìn? Lỗi này đã được mở từ tháng 4 năm 2008! Chúng ta phải làm gì trong lúc này?

Tôi cần tạo một số phân trang và cần đảm bảo dữ liệu sạch sẽ, an toàn cho sql trước khi gửi câu lệnh sql.



Câu trả lời:


165

Tôi nhớ đã gặp vấn đề này trước đây. Truyền giá trị tới một số nguyên trước khi chuyển nó cho hàm liên kết. Tôi nghĩ rằng điều này giải quyết nó.

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

37
Cảm ơn! Nhưng trong PHP 5.3, đoạn mã trên đã gặp lỗi "Lỗi nghiêm trọng: Không thể chuyển tham số 2 bằng tham chiếu". Nó không thích truyền một int ở đó. Thay vì (int) trim($_GET['skip']), hãy thử intval(trim($_GET['skip'])).
Will Martin

5
sẽ thật tuyệt nếu ai đó cung cấp lời giải thích tại sao điều này lại như vậy ... từ quan điểm thiết kế / bảo mật (hoặc khác).
Ross

6
Điều này sẽ chỉ hoạt động nếu các câu lệnh chuẩn bị được mô phỏng được bật . Nó sẽ thất bại nếu nó bị vô hiệu hóa (và nó sẽ bị vô hiệu hóa!)
ma của Madara

4
@Ross Tôi không thể trả lời cụ thể điều này- nhưng tôi có thể chỉ ra rằng LIMIT và OFFSET là các tính năng được dán vào SAU KHI tất cả sự điên rồ của PHP / MYSQL / PDO này tấn công mạch nhà phát triển ... Thực tế, tôi tin rằng chính Lerdorf là ​​người đã giám sát LIMIT triển khai một vài năm trở lại đây. Không, nó không trả lời câu hỏi, nhưng nó chỉ ra rằng nó là một aftermarket add-on, và bạn biết rõ làm thế nào họ có thể làm việc ra đôi khi ....
FredTheWebGuy

2
@Ross PDO không cho phép ràng buộc các giá trị - thay vì các biến. Nếu bạn thử bindParam (': something', 2), bạn sẽ gặp lỗi vì PDO sử dụng một con trỏ đến biến mà một số không thể có (nếu $ i là 2, bạn có thể có một con trỏ hướng tới $ i nhưng không hướng tới số 2).
Kristijan

44

Giải pháp đơn giản nhất là tắt chế độ giả lập. Bạn có thể làm điều đó bằng cách thêm dòng sau

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

Ngoài ra, chế độ này có thể được đặt làm tham số khởi tạo khi tạo kết nối PDO . Nó có thể là một giải pháp tốt hơn vì một số báo cáo rằng trình điều khiển của họ không hỗ trợ setAttribute()chức năng này.

Nó sẽ không chỉ giải quyết vấn đề của bạn với ràng buộc mà còn cho phép bạn gửi các giá trị trực tiếp vào execute(), điều này sẽ làm cho mã của bạn ngắn hơn đáng kể. Giả sử chế độ giả lập đã được thiết lập, toàn bộ sự việc sẽ mất tới nửa tá dòng mã

$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... Tại sao nó không bao giờ là đơn giản đối với tôi :) Mặc dù tôi chắc chắn rằng điều này sẽ giúp hầu hết mọi người ở đó, nhưng trong trường hợp của tôi, tôi đã phải sử dụng một cái gì đó tương tự với câu trả lời được chấp nhận. Xin gửi lời chào tới độc giả trong tương lai!
Matthew Johnson

@MatthewJohnson đó là trình điều khiển nào?
Ý thức chung của bạn

Tôi không chắc, nhưng trong sách hướng dẫn có ghi 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. Nó là một cái mới đối với tôi, nhưng sau đó một lần nữa tôi chỉ mới bắt đầu với PDO. Thường sử dụng mysqli, nhưng tôi đã cố gắng mở rộng tầm nhìn của mình.
Matthew Johnson

@MatthewJohnson nếu bạn đang sử dụng PDO cho mysql, trình điều khiển không hỗ trợ chức năng này. Vì vậy, bạn nhận được thông báo này do một số sai lầm
Common Sense của bạn

1
Nếu bạn nhận được thông báo sự cố hỗ trợ trình điều khiển, hãy kiểm tra lại nếu bạn gọi setAttributecâu lệnh ($ stm, $ stmt) không phải cho đối tượng pdo.
Jehong Ahn 21/12/16

17

Xem xét báo cáo lỗi, cách sau có thể hoạt động:

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

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

nhưng bạn có chắc chắn dữ liệu đến của bạn là chính xác? Bởi vì trong thông báo lỗi, dường như chỉ có một dấu ngoặc kép sau số (trái ngược với toàn bộ số được đặt trong dấu ngoặc kép). Đây cũng có thể là lỗi với dữ liệu đến của bạn. Bạn có thể làm gì print_r($_GET);để tìm hiểu?


1
'' 15 ', 15'. Số đầu tiên được đặt trong dấu ngoặc kép. Số thứ hai không có dấu ngoặc kép nào cả. Vì vậy, có, dữ liệu là tốt.
Nathan H

8

Đây chỉ là bản tóm tắt.
Có bốn tùy chọn để tham số hóa các giá trị LIMIT / OFFSET:

  1. Vô hiệu hóa PDO::ATTR_EMULATE_PREPARESnhư đã đề cập ở trên .

    Điều này ngăn các giá trị được truyền cho ->execute([...])luôn hiển thị dưới dạng chuỗi.

  2. Chuyển sang ->bindValue(..., ..., PDO::PARAM_INT)dân số tham số thủ công.

    Tuy nhiên, điều này kém tiện lợi hơn một -> danh sách thực thi [].

  3. Chỉ cần tạo một ngoại lệ ở đây và chỉ nội suy các số nguyên thuần túy khi chuẩn bị truy vấn SQL.

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

    Việc đúc là quan trọng. Thông thường hơn bạn thấy ->prepare(sprintf("SELECT ... LIMIT %d", $num))được sử dụng cho các mục đích như vậy.

  4. Nếu bạn không sử dụng MySQL, nhưng ví dụ: SQLite hoặc Postgres; bạn cũng có thể ép các tham số liên kết trực tiếp trong SQL.

     SELECT * FROM tbl LIMIT (1 * :limit)

    Một lần nữa, MySQL / MariaDB không hỗ trợ các biểu thức trong mệnh đề LIMIT. Chưa.


1
Tôi sẽ sử dụng sprintf () với% d cho 3, tôi muốn nói rằng nó ổn định hơn một chút sau đó với biến.
hakre

Yeah, varfunc cast + interpolation không phải là ví dụ thực tế nhất. Tôi thường sử dụng lười biếng của mình {$_GET->int["limit"]}cho những trường hợp như vậy.
mario

7

cho LIMIT :init, :end

Bạn cần phải ràng buộc theo cách đó. nếu bạn có một cái gì đó giống như $req->execute(Array());nó sẽ không hoạt động vì nó sẽ truyền PDO::PARAM_STRđến tất cả các vars trong mảng và đối với LIMITbạn, bạn hoàn toàn cần một Số nguyên. bindValue hoặc BindParam như bạn muốn.

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

2

Vì không ai giải thích được tại sao điều này lại xảy ra, tôi đang thêm một câu trả lời. Lý do nó hoạt động như vậy là do bạn đang sử dụng trim(). Nếu bạn xem hướng dẫn sử dụng PHP trim, kiểu trả về là string. Sau đó, bạn đang cố gắng vượt qua điều này với tư cách là PDO::PARAM_INT. Một số cách để giải quyết vấn đề này là:

  1. Sử dụng filter_var($integer, FILTER_VALIDATE_NUMBER_INT)để đảm bảo rằng bạn đang chuyển một số nguyên.
  2. Như những người khác đã nói, sử dụng intval()
  3. Đúc với (int)
  4. Kiểm tra xem nó có phải là một số nguyên với is_int()

Có rất nhiều cách khác, nhưng về cơ bản đây là nguyên nhân gốc rễ.


3
Nó xảy ra ngay cả khi biến luôn là một số nguyên.
felwithe

1

Sự bù đắp và giới hạn bindValue sử dụng PDO :: PARAM_INT và nó sẽ hoạt động


-1

// BEFORE (Hiện lỗi) $ query = ".... LIMIT: p1, 30;"; ... $ stmt-> bindParam (': p1', $ limiteInferior);

// SAU (Đã sửa lỗi) $ query = ".... LIMIT: p1, 30;"; ... $ limiteInferior = (int) $ limiteInferior; $ stmt-> bindParam (': p1', $ limiteInferior, PDO :: PARAM_INT);


-1

PDO::ATTR_EMULATE_PREPARES đã cho tôi

Trình điều khiển không hỗ trợ chức năng này: Trình điều khiển này không hỗ trợ lỗi thiết lập thuộc tính.

Cách giải quyết của tôi là đặt một $limitbiến dưới dạng chuỗi, sau đó kết hợp nó trong câu lệnh chuẩn bị như trong ví dụ sau:

$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ó rất nhiều điều đang xảy ra giữa các phiên bản PHP khác nhau và những điều kỳ lạ của PDO. Tôi đã thử 3 hoặc 4 phương pháp ở đây nhưng không thể làm cho LIMIT hoạt động.
Đề xuất của tôi là sử dụng định dạng / nối chuỗi VỚI bộ lọc intval () :

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

Điều rất quan trọng là sử dụng intval () để ngăn chặn việc đưa vào SQL, đặc biệt nếu bạn nhận được giới hạn của mình từ $ _GET hoặc tương tự. Nếu bạn làm điều đó, đây là cách dễ nhất để LIMIT hoạt động.

Có rất nhiều người nói về 'Vấn đề với LIMIT trong PDO' nhưng suy nghĩ của tôi ở đây là các tham số PDO không bao giờ được đề cập đến để sử dụng cho LIMIT vì chúng sẽ luôn là số nguyên mà một bộ lọc nhanh hoạt động. Tuy nhiên, nó có một chút sai lầm vì triết lý luôn là không tự thực hiện bất kỳ quá trình lọc SQL injection nào mà thay vào đó là 'Nhờ PDO xử lý nó'.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.