如何在LIMIT子句中应用bindValue方法?


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服务器版本相对应的手册,以在第1行的“ 15”,“ 15”附近使用正确的语法

似乎PDO会将单引号添加到SQL代码的LIMIT部分的变量中。我查了一下,发现这个与我相关的错误:http : //bugs.php.net/bug.php?id=44639

那是我在看的吗?自2008年4月以来,此错误已被打开!同时,我们应该做什么?

在发送sql语句之前,我需要构建一些分页,并需要确保数据干净,sql注入安全。



Answers:


165

我记得以前有这个问题。将值转换为整数,然后再将其传递给bind函数。我认为这解决了。

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

37
谢谢!但是在PHP 5.3中,上面的代码引发了一个错误,提示“致命错误:无法通过引用传递参数2”。它不喜欢在此处强制转换为int。相反(int) trim($_GET['skip']),请尝试 intval(trim($_GET['skip']))
Will Martin

5
如果有人从设计/安全性(或其他角度)的角度提供解释为什么如此的话,那会很酷。
罗斯

6
仅当启用了模拟的准备好的语句时,这才起作用。如果将其禁用,它将失败(并且应该将其禁用!)
Madara's Ghost

4
@Ross我不能具体回答这个问题,但是我可以指出LIMIT和OFFSET是在所有PHP / MYSQL / PDO疯狂袭击开发电路之后才粘合的功能...实际上,我相信是Lerdorf亲自监督了几年前实施LIMIT。不,它不能回答问题,但是它确实表明这是售后市场的附加组件,您知道他们有时可以做得很好....
FredTheWebGuy 2013年

2
@Ross PDO不允许绑定值-而是变量。如果您尝试bindParam(':something',2)将会出错,因为PDO使用了一个指向数字无法拥有的变量的指针(如果$ i为2,您可以拥有指向$ i的指针,但不能指向2号)。
2014年

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...为什么对来说从来没有那么简单:)虽然我确定这将吸引大多数人,但就我而言,我最终不得不使用与公认答案相似的方法。请注意未来的读者!
马修·约翰逊

@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,但我想我会拓宽视野。
马修·约翰逊

@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'。第一个数字用引号引起来。第二个数字根本没有引号。是的,数据很好。
内森·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 2015年

是的,varfunc cast + interpolation不是最实际的示例。{$_GET->int["limit"]}在这种情况下,我经常会懒惰。
mario'Mar

7

对于 LIMIT :init, :end

您需要以这种方式进行绑定。如果您有类似的东西$req->execute(Array());将无法正常工作,因为它将PDO::PARAM_STR强制转换为数组中的所有var,并且对于LIMIT您,您绝对需要Integer。您想要的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

使用PDO :: PARAM_INT的bindValue偏移量和限制,它将起作用


-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变量设置为字符串,然后将其组合在prepare语句中,如以下示例所示:

$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有效的最简单方法。

关于“ PDO中的LIMIT问题”的讨论很多,但我的想法是PDO参数永远不会被用于LIMIT,因为它们始终是整数,可以快速使用过滤器。不过,这仍然有些误导,因为其哲学一直是不自己进行任何SQL注入过滤,而是“让PDO处理它”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.