【问题标题】:PHP PDO bug when trying to parameterize SQL LIMIT :offset, :display?尝试参数化 SQL LIMIT 时的 PHP PDO 错误:偏移量,:显示?
【发布时间】:2012-05-11 04:30:30
【问题描述】:

此查询在 phpMyAdmin 中返回 5 个结果:

SELECT * FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT 0,5

这会在 phpMyAdmin 中返回 count=12(这很好,因为有 12 条记录):

SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT 0,5

在我添加两个变量(偏移量、显示)之前,这个函数运行良好,但是现在它不起作用,并且打印出变量给我偏移量=0,显示=5(所以仍然是LIMIT 0,5)。

function getProducts($offset, $display) {
    $sql = "SELECT * FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT ?,?;";
    $data = array((int)$offset, (int)$display);
    $rows = dbRowsCount($sql, $data);
logErrors("getProducts(".$offset.",".$display.") returned ".$rows." rows.");
    if ($rows > 0) {
        dbQuery($sql, $data);
        return dbFetchAll();
    } else {
        return null;
    }
}

它不起作用,因为我的 dbRowsCount(...) 方法返回的是空字符串(愚蠢的 PDOStatement::fetchColumn),所以我将其更改为使用 PDO::FETCH_ASSOC 返回计数并返回 count=0。

这里是计算行数的函数:

function dbRowsCount($sql, $data) {
    global $db, $query;
    $regex = '/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/is';
    if (preg_match($regex, $sql, $output) > 0) {
        $query = $db->prepare("SELECT COUNT(*) AS count FROM {$output[1]}");
logErrors("Regex output: "."SELECT COUNT(*) AS count FROM {$output[1]}");
        $query->setFetchMode(PDO::FETCH_ASSOC);
        if ($data != null) $query->execute($data); else $query->execute();
        if (!$query) {
            echo "Oops! There was an error: PDOStatement returned false.";
            exit;
        }
        $result = $query->fetch();
        return (int)$result["count"];
    } else {
logErrors("Regex did not match: ".$sql);
    }
    return -1;
}

我的错误日志给了我这个程序的输出:

正则表达式输出:SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT ?,?;
getProducts(0,5) 返回 0 行。

如您所见,SQL 并没有畸形,方法输入变量如预期的那样为 0 和 5。

有谁知道出了什么问题?

更新

根据建议,我确实尝试直接执行查询,它返回了正确的结果:

function dbDebugTest() {
    global $db;
    $stmt = $db->query("SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update LIMIT 0,5;");
    $result = $stmt->fetch();
    $rows = (int)$result["count"];
    logErrors("dbDebugTest() returned rows=".$rows);
}

输出:

> dbDebugTest() returned rows=12

根据另一个建议,我将 !=null 更改为 !==null,并且还打印了 $data 数组:

logErrors("Data: ".implode(",",$data));
if ($data !== null) $query->execute($data); else $query->execute();

输出:

> Data: 0,5

但是,dbRowsCount($sql, $data) 仍然为此查询返回 0 行!

更新 2

遵循实现a custom PDOStatement class 的建议,这将允许我在绑定值后输出查询,我发现该函数将在 $query->execute($data) 之后停止,因此不会打印输出,尽管自定义类适用于我程序中的所有其他查询。

更新代码:

function dbRowsCount($sql, $data) {
    global $db, $query;
    $regex = '/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/is';
    if (preg_match($regex, $sql, $output) > 0) {
        $query = $db->prepare("SELECT COUNT(*) AS count FROM {$output[1]}");
logErrors("Regex output: "."SELECT COUNT(*) AS count FROM {$output[1]}");
        $query->setFetchMode(PDO::FETCH_ASSOC);
logErrors("Data: ".implode(",",$data));
        $query->execute($data);
logErrors("queryString:".$query->queryString);
logErrors("_debugQuery():".$query->_debugQuery());
        if (!$query) {
            echo "Oops! There was an error: PDOStatement returned false.";
            exit;
        }
        $result = $query->fetch();
        return (int)$result["count"];
    } else {
logErrors("Regex did not match: ".$sql);
    }
    return -1;
}

输出:

正则表达式输出:SELECT COUNT() AS count FROM tbl_product_category WHERE id=?;
数据:5
queryString:SELECT COUNT(
) AS count FROM tbl_product_category WHERE id=?;
_debugQuery():SELECT COUNT(*) AS count FROM tbl_product_category WHERE id=?;

正则表达式输出:SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT ?,?;
数据:0,5
// 函数停止,_debugQuery 无法输出

更新 3

由于我无法让自定义 PDOStatement 类给我一些输出,我想我会重写 getProducts(...) 类来将参数与命名占位符绑定。

function getProducts($offset, $display) {
    $sql = "SELECT * FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT :offset, :display;";
    $data = array(':offset'=>$offset, ':display'=>$display);
    $rows = dbRowsCount($sql, $data);
logErrors("getProducts(".$offset.",".$display.") returned ".$rows." rows.");
    if ($rows > 0) {
        dbQuery($sql, $data);
        return dbFetchAll();
    } else {
        return null;
    }
}

输出:

正则表达式输出:SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT :offset, :display;
数据:0,5
// 在 $query->execute($data) 之后仍然崩溃,所以 logErrors("getProducts(".$offset."...)) 没有被打印出来

更新 4

此 dbDebugTest 以前使用直接在 SQL 字符串中声明限值 0,5。现在我已经更新它以正确绑定参数:

function dbDebugTest($offset, $display) {
    logErrors("Beginning dbDebugTest()");
    global $db;
    $stmt = $db->prepare("SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update LIMIT :offset,:display;");
    $stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
    $stmt->bindParam(':display', $display, PDO::PARAM_INT);
    if ($stmt->execute()) {
      $result = $stmt->fetch();
      $rows = (int)$result["count"];
      logErrors("dbDebugTest() returned rows=".$rows);
    } else {
      logErrors("dbDebugTest() failed!");
    }
}

函数崩溃,只有这个输出:

开始 dbDebugTest()

更新 5

按照打开错误的建议(默认情况下它们是关闭的),我这样做了:

$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

它本身使更新 4 中的 dbDebugTest() 工作!

开始 dbDebugTest() dbDebugTest() 返回 rows=12

现在我的网络服务器日志中生成了一个错误:

[警告] mod_fcgid:stderr:PHP 致命错误:
未捕获的异常“PDOException”,带有消息“SQLSTATE[42000]:
语法错误或访问冲突:1064 您的 SQL 语法有错误;
检查与您的 MySQL 服务器版本相对应的手册,以获取正确的语法使用
靠近第 1 行的“0”、“5”
在/home/linweb09/b/example.com-1050548206/user/my_program/database/dal.php:36

第 36 行引用dbRowsCount(...) 方法,该行是$query->execute($data)

所以另一种方法getProducts(...) 仍然不起作用,因为它使用这种方法绑定数据并且参数变成了''0'和'5''(这是一个错误吗?)。有点烦人,但我必须在我的 dal.php 中创建一个新方法,以允许自己以更严格的方式绑定参数 - 使用 bindParam

特别感谢@Travesty3 和@eggyal 的帮助!!非常非常感谢。

【问题讨论】:

  • 您是否尝试手动运行查询?喜欢在没有任何参数或包装函数的情况下使用$db->query(xxx)?那它有用吗?
  • 我不明白为什么这被否决了? @kuba 我没有,因为我直接在 phpMyAdmin 中尝试了 SQL,所以我认为它会起作用,但我现在会尝试。
  • @Ozzy:我也不理解反对票(你有我的 +1)。您是否测试过if ($data != null) 以查看执行的路径?不应该在 PHP 中使用 !is_null()!== 吗?
  • @eggyal:应该使用!empty()
  • kuba,是的,它返回 rows=12。我有一种感觉限制参数没有通过关联数组绑定。 @eggyal 我确实尝试过,并且 $data 不为空 p.s 感谢您的支持

标签: php mysql pdo rowcount


【解决方案1】:

根据问题中的更新 2,在 execute 语句之后执行停止,看起来查询失败。查看some PDO documentation 后,默认错误处理设置似乎是 PDO::ERRMODE_SILENT,这将导致您看到的行为。

这很可能是因为您的 LIMIT 子句中的数字在作为参数传入时被放入单引号中,就像在 this post 中发生的那样。

该帖子的解决方案是使用bindValue 方法将参数指定为整数。所以你可能不得不做类似的事情。

看起来您还应该使用 try-catch 块执行查询以捕获 MySQL 错误。


bindValue 方法:

if ($data !== null)
{
    for ($i=0; $i<count($data); $i++)
        $query->bindValue($i+1, $data[$i], PDO::PARAM_INT);
    $query->execute($data);
}
else
    $query->execute();

【讨论】:

  • 我在我的getProducts(...) 方法中将类型转换为 (int),除此之外,对于许多其他具有整数值的查询,我也以同样的方式执行此操作,并且工作正常。我必须看看bindParam,如果这样的话……
  • 是的,您正在对 int 进行类型转换,但诀窍是告诉 PDO 该值是一个 int。为此,您需要使用 PDO::PARAM_INT。要使用它,您需要使用bindValue 方法。
  • 好的,我已经尝试使用 bindParam,输入 INT,但它仍然崩溃。请查看我的更新。
  • @Ozzy:您尝试更改错误处理设置了吗?试试$db-&gt;setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  • 哇,这让 dbDebugTest() 工作了!但是另一种方法不起作用,并且在我的网络服务器日志文件中生成了一个错误,并带有一个奇怪的参数输入(它添加了一个撇号)
【解决方案2】:

您正在测试$data 是否为NULL,使用相等运算符而不是标识运算符(有关各种比较运算符如何处理NULL 值的更多信息,请参阅the PHP manual)。你需要要么使用身份测试===/!==,要么调用is_null()

正如上面提到的@Travesty3,要测试一个数组是否为空,请使用empty()

【讨论】:

  • 这是一个错误,我现在将对其进行修改。但是,我有一次打印了 $data,它给了我“数组”,所以它不为空。
  • 你是对的,但我在 IF 子句之外打印了它,现在又做了一次,它是一个值为 0,5 的数组。我有一种预感,这与 execute($data) 没有正确绑定有关?
  • @eggyal:你是如何确定测试失败的?如果它确实采用了$query-&gt;execute(); 路径,那么如果您尝试使用LIMIT ?,?,MySQL 不会抛出错误吗? (我实际上是在问,因为我对 PDO 没有太多经验)
  • @eggyal 哦,我同意你的观点并在当时改变了它(当我说'你是对的'时)。我刚刚更新了我的帖子以包含信息。
  • @Travesty3: The documentation 很明显,数组与NULL 的松散相等比较始终为true(因此!= 始终为false)。 QED。
猜你喜欢
  • 2012-05-30
  • 2015-06-20
  • 1970-01-01
  • 2021-02-09
  • 2011-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-26
相关资源
最近更新 更多