【问题标题】:Why is PDO converting my bool(false) param to string('')?为什么 PDO 将我的 bool(false) 参数转换为 string('')?
【发布时间】:2021-02-28 14:56:35
【问题描述】:

我在安装新的 MariaDB 10.5.8 时遇到问题。 STRICT_TRANS_TABLES 已设置,当我尝试使用 $sql 时:

'INSERT INTO test (flag) VALUES (?)'

(其中flag 定义为tinyint(1)),var_dump($params) 显示为:

array(1) {
  [0]=>
  bool(false)
}

我收到这条消息:

Incorrect integer value: '' for column `mydb`.`test`.`flag` at row 1

如果我这样做:

'INSERT INTO test (flag) VALUES (false)'

没有参数,它按预期工作。

这是我连接数据库的方式:

$this->PDO = new PDO('mysql:host=' . DB_SERVER . ';dbname=' . DB_NAME . ';charset=utf8mb4', DB_USER, DB_PASSWORD, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
    PDO::ATTR_STRINGIFY_FETCHES  => false,
]);

$this->PDO->query("SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'");

这就是我将查询/参数发送到 MariaDB 的方式:

$stmt = self::$_instance->PDO->prepare($sql);
$stmt->execute($params);

如果我尝试插入 true 而不是 false,一切正常。 true 正在转换为 1false 正在转换为 ''。我做错了什么?

【问题讨论】:

    标签: php mysql pdo mariadb


    【解决方案1】:

    所有值都被视为PDO::PARAM_STR

    https://www.php.net/manual/en/pdostatement.execute.php

    execute() 方法不允许您指定变量的类型。它总是转换成一个字符串,这就是为什么你会得到奇怪的结果:

    php > var_dump((string) true);
    string(1) "1"
    
    php > var_dump((string) false);
    string(0) ""
    

    您可以使用PDOStatement::bindValue() 指定值的类型:

    $statement->bindValue('some_bool', true, PDO::PARAM_INT);
    

    请注意,MySQL 缺少“真正的”布尔类型,这就是我推荐 PDO::PARAM_INT 而不是 PDO::PARAM_BOOL 的原因。

    【讨论】:

    • 完美,谢谢。我多年来一直在使用 PDO,但是在安装了 sql_mode='' 的 MySQL 上(我没有记录这样做,也不记得我的原因)。使用默认 sql_mode 的新安装引发了这个问题。以前我假设所有内容都作为预期类型发送,因为这就是我认为我正在做的事情并且它似乎正在工作......我假设没有 PDO 选项/常量来改变这种行为(类似于我如何设置 PDO::ATTR_STRINGIFY_FETCHES => false用于获取非字符串数据)?
    • @Codemonkey 据我所知,不,确实没有。如果您正在编写/运行大量数据库查询,您可能希望查看一个 ORM 库(Doctrine、Eloquent 等),该库在将变量类型映射到适当的 SQL 行/类型方面完成了大部分繁重的工作。但对于简单的应用程序,在 foreach 循环中调用 bindValue() 可能就足够了。
    • 关于你的最后一句话,据我所知PDO::PARAM_BOOLPDO::PARAM_INT在使用PDO_MySQL时是一样的。
    • 这似乎不适用于 PHP 8+。即使通过了bindValue("some_bool", true, PDO::PARAM_INT),它也会抱怨“列的值超出范围”。
    【解决方案2】:

    一个更简单的解决方案是预先将一个布尔值转换为 int

    $stmt = $PDO->prepare('INSERT INTO test (flag) VALUES (?)');
    $stmt->execute([(int)$flag]);
    

    【讨论】:

    • 没错,MySQL 并不特别关心您插入的是数字字符串而不是整数。但其他数据库可能。另请注意,强制转换基本上会忽略任何错误处理,因为(int) 'whatever' === 0.
    • 一发现“问题”,我就已经在我的数据库包装函数中做了类似的事情,作为我弄清楚发生了什么的权宜之计。只需遍历所有参数并将它们转换为 1 或 0,如果它们是 === true 或 false。
    • @Duroth 据我所知,您的方法也可以转换为整数,但只能转换一次。 YCS 方法先转换为整数,然后转换为字符串,然后再转换为整数,对吗?
    • @Dharman 没有;这种方法将转换为整数,然后转换为字符串。就这样。然后将使用字符串参数执行该语句。这可能导致错误,尽管它很可能不会,至少对于 MySQL 连接。我的方法应该稍微可靠一些。虽然这里有很多猜想。
    • @Duroth 我认为这是关于完整的转换链,PHP 转换为 int,然后 PDO 转换为字符串,最后 mysql 转换为 int
    【解决方案3】:

    这是一个快速的 sn-p,它可以解决在 pdo 参数中将布尔值转换为空字符串的问题

    $params = array_map(fn($param) => is_bool($param) ? intval($param) : $param, $params);
    

    【讨论】:

    • @YourCommonSense 我知道这在理论上可能会导致问题,但这是我在使用布尔参数时想要的行为。理想情况下,$statement->execute($params) 会推断类型,而不是将所有参数视为PDO::PARAM_STR(或者有一种机制可以轻松地做到这一点)。话虽如此,我看不出您所说的示例查询如何应用 DELETE FROM users WHERE email='0' 不会引起任何我能想象的问题,除非您的电子邮件列中有 0
    • 伙计,你是对的。我将您的自动化与另一个案例混淆了。
    猜你喜欢
    • 2017-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-21
    相关资源
    最近更新 更多