如何在 LIMIT 子句中应用 bindValue 方法?
Posted
技术标签:
【中文标题】如何在 LIMIT 子句中应用 bindValue 方法?【英文标题】:How to apply bindValue method in LIMIT clause? 【发布时间】:2022-01-12 08:04:09 【问题描述】:这是我的代码快照:
$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 在 SQL 代码的 LIMIT 部分中为我的变量添加了单引号。我查了一下,发现了这个我认为相关的错误: http://bugs.php.net/bug.php?id=44639
这是我在看的吗?自 2008 年 4 月以来,此错误已打开! 在此期间我们应该做什么?
我需要建立一些分页,并且在发送sql语句之前需要确保数据是干净的,sql注入安全的。
【问题讨论】:
Here is a related quetion but with bindParam instead 在重复问题中值得注意的答案:Parametrized PDO query and `LIMIT` clause - not working [duplicate] (Aug 2013; by Bill Karwin) 【参考方案1】:我记得以前遇到过这个问题。在将值传递给绑定函数之前将其转换为整数。我认为这可以解决它。
$fetchPictures->bindValue(':skip', (int) trim($_GET['skip']), PDO::PARAM_INT);
【讨论】:
谢谢!但是在 PHP 5.3 中,上面的代码抛出了一个错误,说“致命错误:不能通过引用传递参数 2”。它不喜欢在那里投射一个 int 。而不是(int) trim($_GET['skip'])
,试试intval(trim($_GET['skip']))
。
如果有人提供解释为什么会这样......从设计/安全(或其他)的角度来看会很酷。
这仅在启用模拟预编译语句时有效。如果它被禁用,它将失败(它应该被禁用!)
@Ross 我无法具体回答这个问题——但我可以指出,LIMIT 和 OFFSET 是在所有 PHP/MYSQL/PDO 疯狂袭击开发电路之后粘在上面的功能......事实上,我相信几年前是 Lerdorf 本人负责监督 LIMIT 的实施。不,它没有回答这个问题,但它确实表明它是一个售后附加组件,而且你知道它们有时能发挥多大的作用......
@Ross PDO 不允许绑定到值——而是变量。如果您尝试 bindParam(':something', 2) 您将遇到错误,因为 PDO 使用指向数字不能具有的变量的指针(如果 $i 是 2,您可以有一个指向 $i 但不是指向2号)。【参考方案2】:
最简单的解决方案是关闭仿真模式。您只需添加以下行即可完成此操作
$PDO->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
另外,当creating a PDO connection 时,可以将此模式设置为构造函数参数。这可能是一个更好的解决方案,因为有些人报告他们的驱动程序不支持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
...为什么对 me 来说从来没有这么简单 :) 虽然我确信这会让大多数人在那里,但就我而言,我最终得到了使用类似于接受的答案的东西。只是提醒未来的读者!
@MatthewJohnson 是什么驱动程序?
我不确定,但在the manual 中显示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 for mysql,驱动程序确实支持此功能。因此,由于某些错误,您收到此消息
如果您收到驱动程序支持问题消息,请再次检查您是否为语句 ($stm, $stmt) 而不是 pdo 对象调用 setAttribute
。【参考方案3】:
查看错误报告,以下可能有效:
$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);
$fetchPictures->bindValue(':skip', (int)trim($_GET['skip']), PDO::PARAM_INT);
但是您确定您传入的数据是正确的吗?因为在错误消息中,数字后面似乎只有 一个 引号(而不是用引号括起来的整数)。这也可能是您传入数据的错误。可以发print_r($_GET);
了解一下吗?
【讨论】:
''15',15'。第一个数字完全用引号括起来。第二个数字根本没有引号。所以是的,数据很好。【参考方案4】:这只是摘要。 参数化 LIMIT/OFFSET 值有四个选项:
如above 所述禁用PDO::ATTR_EMULATE_PREPARES
。
这可以防止通过 ->execute([...])
传递的值始终显示为字符串。
切换到手动->bindValue(..., ..., PDO::PARAM_INT)
参数填充。
然而,这不如 ->execute list[] 方便。
只需在此处创建一个例外,并在准备 SQL 查询时插入纯整数。
$limit = intval($limit);
$s = $pdo->prepare("SELECT * FROM tbl LIMIT $limit");
选角很重要。更常见的是,->prepare(sprintf("SELECT ... LIMIT %d", $num))
用于此类目的。
如果您不使用 MySQL,而是使用 SQLite 或 Postgres;您也可以直接在 SQL 中强制转换绑定参数。
SELECT * FROM tbl LIMIT (1 * :limit)
同样,MySQL/MariaDB 不支持 LIMIT 子句中的表达式。还没有。
【讨论】:
我会使用 sprintf() 和 %d for 3,我会说它比使用变量更稳定。 是的,varfunc cast+interpolation 并不是最实用的例子。在这种情况下,我经常使用我懒惰的$_GET->int["limit"]
。【参考方案5】:
为LIMIT :init, :end
您需要以这种方式绑定。如果你有类似$req->execute(Array());
的东西,它不会工作,因为它会将PDO::PARAM_STR
转换为数组中的所有变量,而对于LIMIT
,你绝对需要一个整数。
bindValue 或 BindParam 随意。
$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);
【讨论】:
【参考方案6】:由于没有人解释为什么会发生这种情况,我正在添加一个答案。这样做的原因是因为您使用的是trim()
。如果您查看trim
的PHP 手册,返回类型为string
。然后,您尝试将其作为PDO::PARAM_INT
传递。解决此问题的几种方法是:
-
使用
filter_var($integer, FILTER_VALIDATE_NUMBER_INT)
确保您传递的是整数。
正如其他人所说,使用intval()
使用(int)
铸造
用is_int()
检查它是否是一个整数
还有很多方法,但这基本上是根本原因。
【讨论】:
即使变量一直是整数也会发生这种情况。【参考方案7】:使用 PDO::PARAM_INT 的绑定值偏移和限制,它将起作用
【讨论】:
【参考方案8】://之前(当前错误) $query = " .... LIMIT :p1, 30;"; ... $stmt->bindParam(':p1', $limiteInferior);
//之后(已纠正错误) $query = " .... LIMIT :p1, 30;"; ... $limiteInferior = (int)$limiteInferior; $stmt->bindParam(':p1', $limiteInferior, PDO::PARAM_INT);
【讨论】:
【参考方案9】: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 )
...
【讨论】:
【参考方案10】:在不同版本的 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 处理它”。
【讨论】:
以上是关于如何在 LIMIT 子句中应用 bindValue 方法?的主要内容,如果未能解决你的问题,请参考以下文章
如何加快 LIMIT 子句中偏移量较大的 MySQL 查询?
如何在 PDOStatement::bindValue() 中定义变量类型?
如何将 pdo 的准备好的语句用于 order by 和 limit 子句?