引言:“为什么 PDO 不理解我的数组?”
\n每个处理数据库的 PHP 开发者迟早都会遇到需要使用 IN() 运算符执行查询的情况。看起来很简单:传递一个数组值,然后获取过滤后的数据。但这里正是最大的陷阱所在。
下面是一个新手通常会想到的经典示例:
\n<?php\n$ids = [1, 2, 3, 5];\n$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN (:ids)");\n$stmt->execute([':ids' => $ids]); // 错误!\n?>\n然后 PDO 会抛出一个错误:“Array to string conversion” 或者直接返回空结果。为什么会这样?PDO 无法在 SQL 查询中“展开”数组。它期望每个命名占位符对应一个标量值,而不是整个数组。
\n在本文中,我们将分析开发人员在尝试将数组绑定到 IN() 时犯下的常见错误,并展示正确、安全且高效的解决方案。
错误 #1:天真地将数组传递给 execute()
\n这是最常见的错误。开发者认为 PDO 会自动将数组中的每个值代入 IN() 条件中。但正如我们已经发现的,PDO 并非如此工作。
代码中的表现
\n<?php\n$ids = [10, 20, 30];\n$stmt = $pdo->prepare("SELECT * FROM products WHERE id IN (:ids)");\n$stmt->execute([':ids' => $ids]); // 致命错误\n?>\n结果: 出现错误或关于将数组转换为字符串的警告。即使代码“侥幸通过”,PDO 也会将单词 "Array" 代入查询中——你会得到 WHERE id IN ('Array'),这毫无意义。
为什么会这样? PDO 要求每个占位符对应一个值。数组不是一个值,而是一组值。PDO 无法也不应该猜测你想要做什么。
\n\n错误 #2:将 bindValue() 与数组一起使用
\n有时开发者试图通过使用 bindValue() 来欺骗 PDO:
<?php\n$ids = [1, 2, 3];\n$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN (:ids)");\n$stmt->bindValue(':ids', $ids, PDO::PARAM_STR); // 错误!\n$stmt->execute();\n?>\n问题相同:bindValue() 期望一个标量值。数组将被转换为字符串 "Array",查询将无法正确执行。第三个参数 (PDO::PARAM_STR) 无济于事——它仅指定数据类型,但不会改变处理数组的逻辑。
错误 #3:直接字符串拼接(SQL 注入)
\n绝望之下,一些开发者选择了阻力最小的路径——通过 implode() 直接将数组插入查询:
<?php\n$ids = [1, 2, 3];\n$ids_str = implode(',', $ids);\n$stmt = $pdo->query("SELECT * FROM users WHERE id IN ($ids_str)"); // 危险!\n?>\n这有什么危险? 如果数组中的值来自用户(例如,来自 GET 参数或表单),攻击者可以传递 1; DROP TABLE users; --。查询将变成 IN (1; DROP TABLE users; --),你的数据库将被摧毁。这是经典的 SQL 注入。
即使你确信数据是安全的,这种做法也是不良习惯。总有一天你会忘记检查数据来源,后果将是灾难性的。
\n\n正确解决方案:动态占位符
\n现在来说好消息。PDO 允许动态创建占位符。你需要生成一个包含所需数量 ? 或 :id1, :id2, ... 的字符串,并单独传递数组值。
方法 1:使用位置占位符 (?)
\n这是最简单且最流行的方法:
\n<?php\n