为啥 PDO 将我的 bool(false) 参数转换为 string('')?

Posted

技术标签:

【中文标题】为啥 PDO 将我的 bool(false) 参数转换为 string(\'\')?【英文标题】: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 正在转换为 ''。我做错了什么?

【问题讨论】:

【参考方案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 伙计,你是对的。我将您的自动化与另一个案例混淆了。

以上是关于为啥 PDO 将我的 bool(false) 参数转换为 string('')?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个 PDO 语句会默默地失败?

为啥我的 Haskell 函数参数必须是 Bool 类型?

为啥 QProcess 将我的参数中的“=”转换为空格

Mysql BOOL 字段应该使用哪些值:TRUE/FALSE 或 1/0,为啥?

为啥两个向量的大小<bool> bVec = true,false,true,false,true;向量<char> cVec = 'a', 'b', 'c', 'd',

php switch 为啥 bool TRUE 被判断为了 int 1,NULL 成了 string '',而 bool FALSE 正常?